最近公司网站进行升级,项目要用.net mvc,mysql和轻量级orm框架dapper。由于美工页面出不来啊,让我先写简单写写后台的列表,同事说用MvcJqGrid,也得到了架构的同意。
可是不得不说这个相关文档真不多啊,以前用过jqgrid,但是早忘透了。其实MVCJqGrid这个东西是一个HtmlHelper扩展。不多说,先来看看这个东西吧。
文档的参考地址 http://mvcjqgrid.skaele.it/
下载地址 https://github.com/robinvanderknaap/MvcJqGrid
首先项目中应该添加MvcJqGrid引用。
view视图引用@using MvcJqGrid;
因为jqgrid相对配置麻烦,所以有了MvcJqGrid,使用起来也是简单明了:
@(Html.Grid("search") //设置jqgrid容器,search就是其ID .SetCaption("Toolbar Search") //设置主标题 .AddColumn(new Column("ID") //添加一列(对应数据集合的列名) .SetLabel("Id")) //设置列标题 .AddColumn(new Column("NickName")) .AddColumn(new Column("MobilePhone") .SetUrl("/Home/GetPagedList_User") //请求数据的地址 .SetAutoWidth(true) //自动宽度 .SetRowNum(10) //每页条数 .SetRowList(new[] { 10, 15, 20 }) //设置可选每页页数 .SetViewRecords(true) //设置显示条数 .SetPager("pager")) //设置分页,pager就是分页容器ID
这样一个简单的列表分页页面前台展示就出来了。
后台取数据简单的这种套路:
public JsonResult GetPagedList_User( GridSettings gridSettings) { //参数分别为排序字段,排序方式,当前页,每页条数 List<T> list=获取分页列表(gridSettings.SortColumn, gridSettings.SortOrder, gridSettings.PageIndex, gridSettings.PageSize) if(list==null) return Json(new{total=0,page=0,records=0,rows=("")}, JsonRequestBehavior.AllowGet); var jsonData = new { total = 获取总条数(), page =total / gridSettings.PageSize + 1;, records = content.TotalCount, rows = ( from c in list select new { id = c.ID, cell = new[] { c.ID.ToString(), c.NickName, c.MobilePhone, ...... } }).ToArray() }; return Json(jsonData, JsonRequestBehavior.AllowGet); }
展示列表其实很简单,但是查询的时候遇到了麻烦。因为google很多示例都是使用EF这样的操作的,例如:
if (gridSettings.IsSearch) { name = gridSettings.Where.rules.Any(r => r.field == "Name") ? gridSettings.Where.rules.FirstOrDefault(r => r.field == "Name").data : string.Empty; company = gridSettings.Where.rules.Any(r => r.field == "Company") ? gridSettings.Where.rules.FirstOrDefault(r => r.field == "Company").data : string.Empty; } CustomerRepository repository = new CustomerRepository(); var customers = repository.List(name, company, gridSettings.SortColumn, gridSettings.SortOrder);
乍一看看不明觉厉啊。而当需要手写查询,或者用刀轻量级orm框架需要写sql语句时,就有些手足无措了。
索性通过浏览器f12查看网站示例的请求,发现类似这种参数filters=%7B%22groupOp%22%3A%22AND%22%2C%22rules%22%3A%5B%7B%22field%22%3A%22Last+Modified%22%2C%22op%22%3A%22bw%22%2C%22data%22%3A%2208-11-2013%22%7D%5D%7D,经解码得到{"groupOp":"AND","rules":[{"field":"Last+Modified","op":"bw","data":"08-11-2013"}]}。
后来又看其源码,其实filters就是一个过滤条件类,rules是rule类的集合就是匹配的查询条件,rule类有三个属性field,op,data,意思呢就是字段名,查询方式?,值。其实有这三个就够写sql语句了。例如“select * from user where”+ rule.filed+rule.op+rule.data。
op属性目前有14个查询方式,例如:等于,不等于,小于...包含某值,以某值结束等等。这里拼接rule.op字符串要对此查询方式进行解析。
但是这里问题出来了,没有时间范围查询。
这里我找到他的MvcJqGrid.Enums下的SearchOptions枚举,添加一个RangeDate,表示时间范围。然后在用到此枚举的地方加上。MvcJqGrid下的Column类public Column SetSearchOption方法,它是设置搜索条件的方法,我们在最后添加:
/// Sets search option for column /// </summary> /// <param name="searchOption">Search option</param> public Column SetSearchOption(SearchOptions searchOption) { switch (searchOption) { case SearchOptions.Equal: _searchOption = "eq"; break; case SearchOptions.NotEqual: _searchOption = "ne"; break; case SearchOptions.Less: _searchOption = "lt"; break; case SearchOptions.LessOrEqual: _searchOption = "le"; break; case SearchOptions.Greater: _searchOption = "gt"; break; case SearchOptions.GreaterOrEqual: _searchOption = "ge"; break; case SearchOptions.BeginsWith: _searchOption = "bw"; break; case SearchOptions.DoesNotBeginWith: _searchOption = "bn"; break; case SearchOptions.IsIn: _searchOption = "in"; break; case SearchOptions.IsNotIn: _searchOption = "ni"; break; case SearchOptions.EndsWith: _searchOption = "ew"; break; case SearchOptions.DoesNotEndWith: _searchOption = "en"; break; case SearchOptions.Contains: _searchOption = "cn"; break; case SearchOptions.DoesNotContain: _searchOption = "nc"; break; case SearchOptions.RangeDate://新添加的时间范围 _searchOption = "rd"; break; } return this; }
好了,到现在可以了解到查询方式基本有了,但是目前来说MvcJqgrid提供的日期查询js插件是DatePicker,好像没有时间查询。于是我找到了一个好用的东西—>daterangepicker.js
下载地址:https://github.com/dangrossman/bootstrap-daterangepicker
打开一看英文的展示啊,于是我用了最笨最直接的方法,直接修改它源文件。实例中需要引用moment.min.js,和主文件daterangepicker.js。前者是一个日期处理类库,直接找到_month和_weekdays关键字,对应后面字符串Jan_Feb_Mar_Apr_......修改成一月_二月_...你懂得,Sun_Mon_Tue_...改成星期日_星期一_...注意文件保存格式应为Unicode,要不有乱码。daterangepicker我也把能看懂的展示的文字改成了中文,另外精简了下它的展示方式。看起来还不错的样子:
好了,现在就是两者关联了,直接在MvcJqgrid类库中搜索DatePicker关键字,于是又在Column类中第585行找到了,代码如下:
// SearchType datepicker if (_searchType == Searchtype.Datepicker) { if (_searchDateFormat.IsNullOrWhiteSpace()) script.Append( ", dataInit:function(el){$(el).datepicker({changeYear:true, onSelect: function() {var sgrid = $('###gridid##')[0]; sgrid.triggerToolbar();},dateFormat:'dd-mm-yy'});}"); else script.Append( ", dataInit:function(el){$(el).datepicker({changeYear:true, onSelect: function() {var sgrid = $('###gridid##')[0]; sgrid.triggerToolbar();},dateFormat:'" + _searchDateFormat + "'});}"); }
看不太明白,只知道有个回调函数onSelect,这里估计就是要触发Jqgrid的查询事件了,但是确定按钮(未修改前叫Apply)daterangepicker中貌似没有事件啊。只能为它添加一个:
var DateRangePicker = function (element, options, cb) { var hasOptions = typeof options == 'object'; ...... this.cb = function () { }; //添加onApplyClick回调函数,用于jqgrid获取事件 this.onApplyClick = function () { }; ...... //event listeners(找到它,在下面添加一句) if (typeof options.onApplyClick == 'function') { //此处为调用确定按钮回调 this.container.find('.ranges').on('click', 'button.applyBtn', options.onApplyClick); } ...... }
然后修改Column类下的这里部分
// SearchType datepicker if (_searchType == Searchtype.Datepicker) { if (_searchDateFormat.IsNullOrWhiteSpace()) script.Append( ",dataInit:function(el){$(el).daterangepicker({onApplyClick:function(){var sgrid = $('###gridid##')[0]; sgrid.triggerToolbar();}});}"); else script.Append( ",dataInit:function(el){$(el).daterangepicker({format:'" + _searchDateFormat + "',onApplyClick:function(){var sgrid = $('###gridid##')[0]; sgrid.triggerToolbar();}});}"); }
好了。准备工作完了。下面要有一个通用的模板才行。我在MvcJqgrid类库中加入了一个文件夹,名字叫MyExtend。
首先需要一个枚举来标识查询的字段是不是可以用引号的(当然我的理解很粗俗)。OptionType:
/// <summary> /// 查询时候需不需要用引号 /// </summary> public enum OptionType { /// <summary> /// 不用引号 /// </summary> Number = 0, /// <summary> /// 用引号 /// </summary> String = 1 }
其次通过前面说过的后台列表代码,我们知道其实应该有四个属性,列表List<T>,当前页PageIndex,总条数TotalCount,总页数TotalPage。于是起了个名字叫GridContent的实体类:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MvcJqGrid.MyExtend { public class GridContent<T> where T : new() { /// <summary> /// 查询列表 2013.11.27 /// </summary> public List<T> GList { get; set; } /// <summary> /// 总条数 /// </summary> public int TotalCount { get; set; } /// <summary> /// 总页数 /// </summary> public int TotalPage { get; set; } /// <summary> /// 当前页 /// </summary> public int PageIndex { get; set; } } }
然后就是我们要提取出来一个接口,作为获取根据条件查询的数据列表和根据条件查询的总条数。起了名字叫ICanSetGrid:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace MvcJqGrid.MyExtend { /// <summary> /// 设置jqgrid的接口。后台类用到要查询列表时需实现此接口 2013.11.27 /// </summary> /// <typeparam name="T"></typeparam> public interface ICanSetGrid<T> where T : new() { /// <summary> /// 获取分页列表 /// </summary> /// <param name="sidx">排序字段</param> /// <param name="sort">排序方式</param> /// <param name="page">当前页</param> /// <param name="rows">每页条数</param> /// <param name="where">查询条件</param> /// <returns></returns> List<T> GetPagedList(string sidx, string sort, int page, int rows, string where = null); /// <summary> /// 获取总条数 /// </summary> /// <param name="where">查询条件</param> /// <returns></returns> int GetTotalCount(string where=null); } }
最后就是主要的了,起名为JqGridHelper:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Text.RegularExpressions; 6 using System.Threading; 7 using System.Web.Mvc; 8 9 namespace MvcJqGrid.MyExtend 10 { 11 /// <summary> 12 /// jqgrid帮助,controller用到GetGridContent方法 2013.11.27 13 /// </summary> 14 public class JqGridHelper 15 { 16 17 /// <summary> 18 /// jqgrid匹配查询条件 19 /// </summary> 20 /// <param name="oType">字符串类型或者值类型</param> 21 /// <param name="searchOption">MvcJqGrid.Enums.SearchOptions对应的搜索条件字符串(搜索规则MvcJqGrid.Rule对应的op属性)</param> 22 /// <param name="column">搜索字段</param> 23 /// <param name="value">搜索值</param> 24 /// <returns>例如ID!=5,Phone like '%1581111222%'</returns> 25 private static string FormatSearchTerm(OptionType oType, string searchOption, string column, string value) 26 { 27 StringBuilder str = new StringBuilder(); 28 switch (searchOption) 29 { 30 case "eq"://等于 31 return oType == OptionType.Number ? str.Append(column).Append("=").Append(value).ToString() : str.Append(column).Append("='").Append(value).Append("'").ToString(); 32 33 case "ne"://不等于 34 return oType == OptionType.Number ? str.Append(column).Append("<>").Append(value).ToString() : str.Append(column).Append("<>'").Append(value).Append("'").ToString(); 35 36 case "lt"://小于 37 return oType == OptionType.Number ? str.Append(column).Append("<").Append(value).ToString() : str.Append(column).Append("<'").Append(value).Append("'").ToString(); 38 39 case "le"://小于等于 40 return oType == OptionType.Number ? str.Append(column).Append("<=").Append(value).ToString() : str.Append(column).Append("<='").Append(value).Append("'").ToString(); 41 42 case "gt"://大于 43 return oType == OptionType.Number ? str.Append(column).Append(">").Append(value).ToString() : str.Append(column).Append(">'").Append(value).Append("'").ToString(); 44 45 case "ge"://大于等于 46 return oType == OptionType.Number ? str.Append(column).Append(">=").Append(value).ToString() : str.Append(column).Append(">='").Append(value).Append("'").ToString(); 47 48 case "bw"://从某处开始查询 49 return str.Append(column).Append(" like '").Append(value).Append("%'").ToString(); 50 51 case "bn"://不从某处开始查询 52 return str.Append(column).Append(" not like '").Append(value).Append("%'").ToString(); 53 54 case "in"://包含某值 55 return str.Append(column).Append(" in (").Append(value).Append(")").ToString(); 56 57 case "ni"://不包含某值 58 return str.Append(column).Append(" not in (").Append(value).Append(")").ToString(); 59 60 case "ew"://以某值结束 61 return str.Append(column).Append(" like '%").Append(value).Append("'").ToString(); 62 63 case "en"://不以某值结束 64 return str.Append(column).Append(" not like '%").Append(value).Append("'").ToString(); 65 66 case "cn"://包含,全字匹配 67 return str.Append(column).Append(" like '%").Append(value).Append("%'").ToString(); 68 69 case "nc"://不包含 70 return str.Append(column).Append(" not like '%").Append(value).Append("%'").ToString(); 71 72 case "rd"://日期范围 73 return GetRangeDateSearchTerm(column, value); 74 75 default://默认以某处开始 76 return str.Append(column).Append(" like '").Append(value).Append("%'").ToString(); 77 } 78 } 79 /// <summary> 80 /// 获取设置jqgrid的GridContent对象 81 /// </summary> 82 /// <typeparam name="T">操作的类</typeparam> 83 /// <param name="type">此处用到仅是获取类型,所以只传入T的一个实例就可以</param> 84 /// <param name="gridSettings">前台到后台的jqgrid参数</param> 85 /// <param name="SetGrid">实现ICanSetGrid接口的bll操作类</param> 86 /// <returns></returns> 87 public static GridContent<T> GetGridContent<T>(GridSettings gridSettings, ICanSetGrid<T> SetGrid) where T:new() 88 { 89 GridContent<T> content = new GridContent<T>(); 90 if (gridSettings.IsSearch && gridSettings.Where != null)//有查询时候 91 { 92 StringBuilder rulestr = new StringBuilder(); 93 var rules = gridSettings.Where.rules; 94 for (int i = 0; i < rules.Length; i++) 95 { 96 rulestr.Append(JqGridHelper.FormatSearchTerm(JqGridHelper.GetOptionType(new T().GetType(), rules[i].field), rules[i].op, rules[i].field, rules[i].data)); 97 98 rulestr.Append(" and "); 99 } 100 rulestr.Remove(rulestr.Length - 5, 4); 101 content.GList = SetGrid.GetPagedList(gridSettings.SortColumn, gridSettings.SortOrder, gridSettings.PageIndex, gridSettings.PageSize,rulestr.ToString()); 102 content.TotalCount = SetGrid.GetTotalCount(rulestr.ToString()); 103 } 104 else//没有查询时候 105 { 106 content.GList = SetGrid.GetPagedList(gridSettings.SortColumn, gridSettings.SortOrder, gridSettings.PageIndex, gridSettings.PageSize, null); 107 content.TotalCount = SetGrid.GetTotalCount(null); 108 } 109 //如果总条数和每页数相等,那么总页数就是2页,所以判断是一页 110 content.TotalPage = (content.TotalCount <= gridSettings.PageSize ? 0 : content.TotalCount) / gridSettings.PageSize + 1; 111 content.PageIndex = gridSettings.PageIndex; 112 return content; 113 } 114 /// <summary> 115 /// 获取类型属性 116 /// </summary> 117 /// <param name="localType">当前类类型</param> 118 /// <param name="columnName">属性名</param> 119 /// <returns></returns> 120 private static OptionType GetOptionType(Type localType, string columnName) 121 { 122 foreach (var item in localType.GetProperties()) 123 { 124 if (item.Name == columnName) 125 { 126 string typeName = item.PropertyType.Name; 127 //数字返回,不需要引号 128 if (typeName.Contains("Int") || typeName.Contains("Double") || typeName.Contains("Decimal") || typeName.Contains("Single")) 129 { 130 return OptionType.Number; 131 } 132 //返回需要引号类型 133 return OptionType.String; 134 } 135 continue; 136 } 137 return OptionType.String; 138 } 139 /// <summary> 140 /// 日期范围条件 141 /// </summary> 142 /// <param name="column">要进行日期范围查询的字段名</param> 143 /// <param name="inputRange">查询值</param> 144 /// <returns></returns> 145 private static string GetRangeDateSearchTerm(string column, string inputRange) 146 { 147 Regex r = new Regex(@"(\d{8}) - (\d{8})"); 148 if (!r.IsMatch(inputRange)) 149 return column + " like '" + inputRange + "'";//如果不符合范围则匹配模糊查询 150 Match m = r.Match(inputRange); 151 string start = DateTime.ParseExact(m.Groups[1].Value, "yyyyMMdd", Thread.CurrentThread.CurrentCulture).ToString(); 152 string end = DateTime.ParseExact(m.Groups[2].Value, "yyyyMMdd", Thread.CurrentThread.CurrentCulture).AddSeconds(86399).ToString(); 153 return column + " between '" + start + "' and '" + end + "' "; 154 } 155 } 156 }
下面是例子,首先是相关Bll层的操作类实现ICanSetGrid<T>,两个方法对应Dao层的方法应该不难吧,这里举个栗子:
/// <summary> /// 获得数据列表 /// </summary> public List<UserInfo> GetEntityList(string strWhere) { try { StringBuilder strSql = new StringBuilder(); strSql.Append("select * FROM UserInfo "); if (strWhere.Trim() != "") { strSql.Append(" where " + strWhere); } IEnumerable<UserInfo> list = null; using (var conn = DbConnect.MysqlConnectObj()) { list = conn.Query<UserInfo>(strSql.ToString(), null); } if (list != null && list.Count() > 0) return list.ToList(); return null; } catch (Exception e) { throw new LDSystemException("UserInfo0x15", "系统错误", e); } } /// <summary> /// 分页获取查询所得到的用户列表 /// </summary> /// <param name="where">查询条件,默认为1=1</param> /// <param name="sidx">排序字段</param> /// <param name="sort">升序降序</param> /// <param name="page">当前页</param> /// <param name="rows">每页条数</param> /// <returns></returns> public List<UserInfo> GetEntityPagedList(string where, string sidx, string sort, int page, int rows) { return GetEntityList((string.IsNullOrEmpty(where) ? " 1=1 " : where) + "order by " + sidx + " " + sort + " limit " + (page - 1) * rows + "," + rows); } /// <summary> /// 获取用户数量 /// </summary> /// <param name="where">条件</param> /// <returns></returns> public int GetTotalCount(string where) { try { StringBuilder sb = new StringBuilder(); sb.Append("select count(1) as Count from UserInfo "); if (!string.IsNullOrEmpty(where)) { sb.Append(" where "); sb.Append(where); } using (var conn = DbConnect.MysqlConnectObj()) { var query = conn.Query(sb.ToString()).Single(); if (query == null) { return 0; } else { return (int)query.Count; } } } catch (Exception e) { throw new LDSystemException("UserInfo0x25", "系统错误", e); } }
然后是Controller中的例子了:
public JsonResult GetPagedList_User( GridSettings gridSettings) { GridContent<UserInfo> content = JqGridHelper.GetGridContent<UserInfo>(gridSettings, UserInfoBll.CreateInstance()); if(content.GList==null) return Json(new{total=0,page=0,records=0,rows=("")}, JsonRequestBehavior.AllowGet); var jsonData = new { total = content.TotalPage, page = content.PageIndex, records = content.TotalCount, rows = ( from c in content.GList select new { id = c.UserID, cell = new[] { c.UserID.ToString(), c.NickName, c.Phone, c.EMail, c.LastLoginTime.ToString("yyyy-MM-dd HH:mm"), c.isAct==1?"激活":"未激活", } }).ToArray() }; return Json(jsonData, JsonRequestBehavior.AllowGet); }
最后来看下页面展示吧:
@using MvcJqGrid; @{ ViewBag.Title = "UserList"; } <link href="~/Content/jqgrid/css/jquery-ui-custom.min.css" rel="stylesheet" /> <link href="~/Content/jqgrid/css/ui.jqgrid.css" rel="stylesheet" /> <link href="~/Content/bootstrap/css/bootstrap.min.css" rel="stylesheet" /> <link href="~/Content/daterangepicker/css/daterangepicker-bs3.css" rel="stylesheet" /> <script src="~/Scripts/jquery-1.8.3.min.js"></script> <script src="~/Content/jqgrid/js/grid.locale-cn.js"></script> <script src="~/Content/jqgrid/js/jquery.jqGrid.min.js"></script> <script src="~/Content/daterangepicker/js/moment.min.js" charset="utf-8"></script> <script src="~/Content/daterangepicker/js/daterangepicker.js"></script> <h2>用户列表</h2> @(Html.Grid("UserGrid")//定义容器ID .SetCaption("用户列表")//设置标题 .AddColumn(new Column("UserID").SetLabel("ID").SetSearch(false)) .AddColumn(new Column("NickName").SetLabel("昵称").SetSearchOption(MvcJqGrid.Enums.SearchOptions.Contains)) .AddColumn(new Column("Phone").SetLabel("手机号").SetSearchOption(MvcJqGrid.Enums.SearchOptions.Contains)) .AddColumn(new Column("EMail").SetLabel("邮箱").SetSearchOption(MvcJqGrid.Enums.SearchOptions.Contains)) .AddColumn(new Column("LastLoginTime").SetLabel("最后登录时间").SetSearchType(MvcJqGrid.Enums.Searchtype.Datepicker).SetSearchOption(MvcJqGrid.Enums.SearchOptions.RangeDate)) .AddColumn(new Column("isAct").SetLabel("是否激活").SetSearchType(MvcJqGrid.Enums.Searchtype.Select).SetSearchTerms(new Dictionary<string, string>() { {"0","未激活"},{"1","激活"}}).SetSearchOption(MvcJqGrid.Enums.SearchOptions.Equal)) .SetUrl(Url.Action("GetPagedList_User", "UserInfo")).SetRowNumbers(true)//设置取数据的地址 .SetSortName("UserID")//默认排序 .SetAutoWidth(true) .SetHeight(450) .SetRowNum(10)//设置每页条数 .SetRowList(new[] { 10, 15, 20 })//可选择每页条数 .SetViewRecords(true)//显示条数 .SetSearchToolbar(true).SetSearchOnEnter(true)//设置可以搜索 .SetPager("pager")//设置分页 .SetLoadText("请等待") .SetMultiSelect(true) .SetToolbar(true).SetToolbarPosition(MvcJqGrid.Enums.ToolbarPosition.Bottom) )
好了,大概就是这么个样子。数据库拼接字符串查询确实不太好,我再看看别的方法。
另外bootstrap是个好东西啊,建议大家看看。