【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构

时间:2022-12-23 12:43:33

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)

 @{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - OSharp管理系统</title>
<link href="/Content/themes/gray/easyui.css" rel="stylesheet" />
<link href="/Content/themes/icon.css" rel="stylesheet" />
<link href="/Content/osharp-icons.css" rel="stylesheet" />
<link href="/Content/osharp-admin.css" rel="stylesheet"/>
@RenderSection("header", false)
</head>
<body>
@RenderBody()
<script src="/Scripts/jquery-1.11.1.js" type="text/javascript"></script>
<script src="/Scripts/jquery.easyui-1.4.1.js" type="text/javascript"></script>
<script src="/Scripts/locale/easyui-lang-zh_CN.js" type="text/javascript"></script>
<script src="/Scripts/json2.js" type="text/javascript"></script>
<script src="/Scripts/osharp.global.js" type="text/javascript"></script>
<script src="/Scripts/osharp.easyui.js" type="text/javascript"></script>
<script src="/Scripts/osharp.data.js" type="text/javascript"></script>
@RenderSection("footer", false)
</body>
</html>

后台的EasyUI-Layout布局

  一般来说,后台管理页面都是这样一个布局方式:

  1. 上边一个顶栏
  2. 左边一个手风琴或树形的导航栏
  3. 中间是一个由iframe加载具体内容页的多选项卡tab页面

  这样,就要用到easyui的easyui-layout来做整体布局,左边的导航栏使用easyui-accordion,右边加载内容页的多选项卡使用easyui-tabs。easyui的布局在网上也很普遍,具体的就不说了,完整代码如下:

 @{
ViewBag.Title = "OSharp后台管理";
Layout = "~/Areas/Admin/Views/Shared/_Layout.cshtml";
string navDataUrl = Url.Action("GetNavData");
}
<div class="easyui-layout" data-options="fit:true">
<div data-options="region:'north', height:50" style="padding: 10px;">
<span style="font-size: 18px;">OSharp后台管理系统</span>
<a href="/" target="_blank">返回首页</a>
</div>
<div data-options="region:'west', split:true, minWidth:100, width:150, title:'导航菜单'">
<div id="main-nav" class="easyui-accordion" data-options="fit:true, border:false, selected:true"> </div>
</div>
<div data-options="region:'center'">
<div id="main-tab" class="easyui-tabs" data-options="fit:true, border:false">
<div title="我的主页" iconcls="pic_209" style="padding: 5px;">
<iframe width="100%" height="100%" frameborder="0" src="@Url.Action("Welcome")" marginheight="0" marginwidth="0"></iframe>
</div>
</div>
</div>
<div data-options="region:'south', height:50">
<p style="text-align:center; line-height:20px;">Copyright &copy; OSharp @DateTime.Now.Year</p>
</div>
</div>
<div id="tab-menu" class="easyui-menu" style="width: 150px;">
<div id="tab-menu-refresh" data-options="iconCls:'icon-reload'">刷新</div>
<div id="tab-menu-openFrame" data-options="iconCls:'pic_138'">新窗口打开</div>
<div class="menu-sep"></div>
<div id="tab-menu-close" data-options="iconCls:'icon-remove'">关闭</div>
<div id="tab-menu-closeleft" data-options="iconCls:'icon-undo'">关闭左边</div>
<div id="tab-menu-closeright" data-options="iconCls:'icon-redo'">关闭右边</div>
<div class="menu-sep"></div>
<div id="tab-menu-closeother" data-options="iconCls:'pic_101'">关闭其他</div>
<div id="tab-menu-closeall" data-options="iconCls:'pic_283'">关闭所有</div>
</div> @section footer{
<script type="text/javascript">
$(function() {
$.getJSON("@navDataUrl", function(data) {
if (data.length == 0) {
return;
}
//第一层生成手风琴的项
$.each(data, function(i, item) {
var id = item.Id;
$("#main-nav").accordion("add", {
title: item.Text,
content: "<ul id='tree-" + id + "'></ul>",
selected: true,
iconCls: item.IconCls
});
$.parser.parse();
//第二层生成树节点
if (!item.Children || item.Children.length == 0) {
return true;
}
var treeData = transToTreeData(item.Children);
$("#tree-" + id).tree({
data: treeData,
onClick: function(node) {
if (node.attributes) {
var tabTitle = node.text;
var url = node.attributes.url;
var icon = node.iconCls;
addTab(tabTitle, url, icon);
}
}
});
});
}); $("#main-tab").tabs({
onContextMenu: function(e, title) {
e.preventDefault();
$("#tab-menu").menu("show", { left: e.pageX, top: e.pageY })
.data("tabTitle", title); //将点击的Tab标题加到菜单数据中
}
}); $("#tab-menu").menu({
onClick: function(item) {
tabHandle(this, item.id);
}
});
}); function addTab(title, url, icon) {
var $mainTabs = $("#main-tab");
if ($mainTabs.tabs("exists", title)) {
$mainTabs.tabs("select", title);
} else {
$mainTabs.tabs("add", {
title: title,
closable: true,
icon: icon,
content: createFrame(url)
});
}
} function createFrame(url) {
var html = '<iframe scrolling="auto" frameborder="0" src="' + url + '" style="width:100%;height:99%;"></iframe>';
return html;
} function tabHandle(menu, type) {
var title = $(menu).data("tabTitle");
var $tab = $("#main-tab");
var tabs = $tab.tabs("tabs");
var index = $tab.tabs("getTabIndex", $tab.tabs("getTab", title));
var closeTitles = [];
switch (type) {
case "tab-menu-refresh":
var iframe = $(".tabs-panels .panel").eq(index).find("iframe");
if (iframe) {
var url = iframe.attr("src");
iframe.attr("src", url);
}
break;
case "tab-menu-openFrame":
var iframe = $(".tabs-panels .panel").eq(index).find("iframe");
if (iframe) {
window.open(iframe.attr("src"));
}
break;
case "tab-menu-close":
closeTitles.push(title);
break;
case "tab-menu-closeleft":
if (index == 0) {
$.osharp.easyui.msg.tip("左边没有可关闭标签。");
return;
}
for (var i = 0; i < index; i++) {
var opt = $(tabs[i]).panel("options");
if (opt.closable) {
closeTitles.push(opt.title);
}
}
break;
case "tab-menu-closeright":
if (index == tabs.length - 1) {
$.osharp.easyui.msg.tip("右边没有可关闭标签。");
return;
}
for (var i = index + 1; i < tabs.length; i++) {
var opt = $(tabs[i]).panel("options");
if (opt.closable) {
closeTitles.push(opt.title);
}
}
break;
case "tab-menu-closeother":
for (var i = 0; i < tabs.length; i++) {
if (i == index) {
continue;
}
var opt = $(tabs[i]).panel("options");
if (opt.closable) {
closeTitles.push(opt.title);
}
}
break;
case "tab-menu-closeall":
for (var i = 0; i < tabs.length; i++) {
var opt = $(tabs[i]).panel("options");
if (opt.closable) {
closeTitles.push(opt.title);
}
}
break;
}
for (var i = 0; i < closeTitles.length; i++) {
$tab.tabs("close", closeTitles[i]);
}
} function transToTreeData(data) {
return $.Enumerable.From(data).Select(function(m) {
var obj = {};
obj.id = m.Id;
obj.text = m.Text;
obj.iconCls = m.IconCls;
obj.checked = m.Checked;
if (m.Url) {
obj.attributes = { url: m.Url };
}
if (m.Children && m.Children.length > 0) {
obj.children = transToTreeData(m.Children);
}
return obj;
}).ToArray();
}
</script>
}

  效果如下:

  【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构

左导航数据加载

  由上面的代码可知,左边导航菜单,完全是由JS解析后端返回的JSON数据来构建,使用后端来返回数据,而不是在前端构建菜单数据,主要是便于将来进行权限控制,后端可以根据当前用户的权限返回特定的菜单数据。后端代码如下:

 [AjaxOnly]
public ActionResult GetNavData()
{
List<TreeNode> nodes = new List<TreeNode>()
{
new TreeNode()
{
Text = "权限",
IconCls = "pic_26",
Children = new List<TreeNode>()
{
new TreeNode() { Text = "用户管理", IconCls = "pic_5", Url = Url.Action("Index", "Users") },
new TreeNode() { Text = "角色管理", IconCls = "pic_198", Url = Url.Action("Index", "Roles") },
new TreeNode() { Text = "组织机构管理", IconCls = "pic_93", Url = Url.Action("Index", "Organizations") },
}
},
new TreeNode()
{
Text = "系统",
IconCls = "pic_100",
Children = new List<TreeNode>()
{
new TreeNode() { Text = "操作日志", IconCls = "pic_125", Url = Url.Action("Index", "OperateLogs") },
new TreeNode() { Text = "系统日志", IconCls = "pic_101", Url = Url.Action("Index", "SystemLogs") },
new TreeNode() { Text = "系统设置", IconCls = "pic_89", Url = Url.Action("Index", "SystemSettings") }
}
}
}; Action<ICollection<TreeNode>> action = list =>
{
foreach (var node in list)
{
node.Id = "node" + node.Text;
}
}; foreach (var node in nodes)
{
node.Id = "node" + node.Text;
if (node.Children != null && node.Children.Count > 0)
{
action(node.Children);
}
} return Json(nodes, JsonRequestBehavior.AllowGet);
}

  上面的代码中,添加了一个 [AjaxOnly],作用标记此方法只允许AJAX的调用方式,拦截非Ajax调用,在数据安全上能起到一定的作用。

 /// <summary>
/// 限制当前功能只允许以Ajax的方式来访问
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AjaxOnlyAttribute : ActionFilterAttribute
{
/// <summary>
/// Called before an action method executes.
/// </summary>
/// <param name="filterContext">The filter context.</param>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new ContentResult
{
Content = Resources.Mvc_ActionAttribute_AjaxOnlyMessage
};
}
}
}

  打上此自定义属性后,如果使用非AJAX的方式来调用上面的GetNavData代码,无法得到返回的JSON数据

  【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构

  正确解析返回数据后,构建导航菜单,点击菜单后打开相应的选项卡

  【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构

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 大致结构如下,请结合注释进行理解:

 @{
Layout = "~/Areas/Admin/Views/Shared/_Layout.cshtml";
string toolbarItem = ViewBag.ToolbarItem ?? "add,edit,save,cancel,delete";
}
@section header
{
<style type="text/css">
html { font-family: sans-serif; }
.datagrid-header-inner { font-weight: bold; }
</style>
}
@section footer
{
@*这里进行变量初始化*@
<script type="text/javascript">
//定义及初始化变量
var rownumbers = true, singleSelect = false, ctrlSelect = true, multiSort = false, pageSize = 25;
var grid, frozenColumns = [[]], columns = [[]], ... //前置逻辑,将在构造datagrid之前执行
var startfunction = function() { };
//后置逻辑,将在构造datagrid之后执行
var endfunction = function() { }; </script> @*开放一个Section,让子视图(Partial View)可以插入代码,对上面定义的变量进行重新赋值。*@
@RenderSection("customScript", true) @*这里才正在执行业务逻辑*@
<script type="text/javascript">
$(function () {
//执行前置逻辑
startfunction(); //构造 datagrid
grid = $("#grid-@ViewBag.GridId").datagrid({
title: "@ViewBag.Title",
fit: true,
frozenColumns: frozenColumns,
columns: columns,
fitColumns: false,
url: "@ViewBag.GridDataUrl",
...
}); //执行后置逻辑
endfunction();
});
</script>
}
@* 后台还有可能有需要执行的逻辑,开放一个Section *@
@RenderSection("endScript", false)
}
@* datagrid 前面有可能需要插入html,开放一个Section *@
@RenderSection("headHtml", false)
<div id="grid-@ViewBag.GridId"></div>
@* datagrid 后面有可能需要插入html,开放一个Section *@
@RenderSection("footHtml", false)

  结合实际需求,OSharp中定义的一个可用的 _DataGridLayout.cshtml 如下,可以根据需求进行更改:

 @{
Layout = "~/Areas/Admin/Views/Shared/_Layout.cshtml";
string toolbarItem = ViewBag.ToolbarItem ?? "add,edit,save,cancel,delete";
}
@section header{
<style type="text/css">
html {
font-family: sans-serif;
} .datagrid-header-inner {
font-weight: bold;
}
</style>
}
@section footer{
<script src="/Scripts/plugins/datagrid-filter.js" type="text/javascript"></script>
<script src="/Scripts/plugins/datagrid-detailview.js" type="text/javascript"></script>
<script type="text/javascript">
var rownumbers = true, singleSelect = false, ctrlSelect = true, multiSort = false, pageSize = 25;
var grid, frozenColumns = [[]], columns = [[]], filterData = [], enableFilterData = false, editIndex = undefined, columnMenu = undefined; var startfunction = function () {
};
var endfunction = function () {
};
var addObject = function () {
return {};
};
var replaceSearchField = function (field) {
return field;
};
</script>
@RenderSection("paramInit", false)
<script type="text/javascript">
function formatBoolean(value) {
var icon = value ? 'checkmark' : 'checknomark';
return '<span class="tree-file icon-' + icon + '"></span>';
} var addNewRow = function () {
if (!endEditing()) {
$.osharp.easyui.msg.tip("请先提交或取消正在编辑的行。");
return;
}
grid.datagrid("appendRow", addObject() || {});
editIndex = grid.datagrid("getRows").length - 1;
grid.datagrid("selectRow", editIndex)
.datagrid("beginEdit", editIndex);
}; var beginEdit = function () {
var row = grid.datagrid("getSelected");
if (!row) {
$.osharp.easyui.msg.tip("请选择要编辑的行。");
return;
}
var index = grid.datagrid("getRowIndex", row);
beginEditRow(index);
}; var beginEditRow = function (index) {
@if (toolbarItem == null || !toolbarItem.Contains(",save"))
{
@Html.Raw("return;")
} if (endEditing()) {
grid.datagrid("selectRow", index)
.datagrid("beginEdit", index);
editIndex = index;
} else {
grid.datagrid("unselectRow", index)
.datagrid("selectRow", editIndex);
}
}; var cancelEdit = function () {
grid.datagrid("rejectChanges");
editIndex = undefined;
}; var saveChanges = function () {
if (!endEditing()) {
return;
}
var adds = grid.datagrid("getChanges", "inserted");
if (adds && adds.length > 0) {
submitAdds(adds);
}
var edits = grid.datagrid("getChanges", "updated");
if (edits && edits.length > 0) {
submitEdits(edits);
}
}; var deleteRows = function () {
var selectRows = grid.datagrid("getSelections");
if (selectRows.length == 0) {
$.osharp.easyui.msg.tip("请先选中要删除的行。");
return;
}
var ids = $.Enumerable.From(selectRows).Select(function (m) { return m.Id; }).ToArray();
$.osharp.easyui.msg.confirm("是否要删除所有选中的行?此操作是不可恢复的。", null, function () {
$.post("@ViewBag.DeleteUrl", { ids: JSON.stringify(ids) }, ajaxResultHandler);
});
}; function endEditing() {
if (editIndex == undefined) {
return true;
}
if (grid.datagrid("validateRow", editIndex)) {
grid.datagrid("endEdit", editIndex);
editIndex = undefined;
return true;
} else {
return false;
}
} function submitAdds(objs) {
$.post("@ViewBag.AddUrl", { dtos: JSON.stringify(objs) }, ajaxResultHandler);
} function submitEdits(objs) {
$.post("@ViewBag.EditUrl", { dtos: JSON.stringify(objs) }, ajaxResultHandler);
} function ajaxResultHandler(data) {
if (data.Type == "Success") {
grid.datagrid("reload");
}
if (data.Type == "Error") {
$.osharp.easyui.msg.error(data.Content);
} else {
$.osharp.easyui.msg.tip(data.Content);
}
} var toolbarData = [
@if (toolbarItem.Contains("add"))
{
@:{ text: "增加", iconCls: "icon-add", handler: addNewRow },
}
@if (toolbarItem.Contains("edit"))
{
<text>
{ text: "编辑", iconCls: "icon-edit", handler: beginEdit },
"-",
</text>
}
@if (toolbarItem.Contains("save"))
{
@:{ text: "保存", iconCls: "icon-save", handler: saveChanges },
}
@if (toolbarItem.Contains("cancel"))
{
<text>
{ text: "取消", iconCls: "icon-undo", handler: cancelEdit },
"-",
</text>
}
@if (toolbarItem.Contains("delete"))
{
@:{ text: "删除", iconCls: "icon-remove", handler: deleteRows },
}
];
</script>
@RenderSection("customScript", true)
<script type="text/javascript">
$(function () {
startfunction(); grid = $("#grid-@ViewBag.GridId").datagrid({
title: "@ViewBag.Title",
fit: true,
frozenColumns: frozenColumns,
columns: columns,
fitColumns: false,
url: "@ViewBag.GridDataUrl",
loadMsg: "正在加载数据,请稍候",
toolbar: toolbarData,
rownumbers: rownumbers,
singleSelect: singleSelect,
ctrlSelect: ctrlSelect,
multiSort: multiSort,
pagination: true,
pageSize: pageSize,
pageList: [10, 25, 50, 100, 200],
remoteFilter: true,
onBeforeLoad: beforeLoad,
loadFilter: loadFilter,
onLoadError: loadError,
onDblClickRow: beginEditRow,
onHeaderContextMenu: headerContextMenu,
showFooter: true
});
if (enableFilterData) {
grid.datagrid("enableFilter", filterData);
} endfunction();
}); //Header右键
function headerContextMenu(e) {
e.preventDefault();
if (!columnMenu) {
createColumnMenu();
}
columnMenu.menu("show", { left: e.pageX, top: e.pageY });
} function createColumnMenu() {
columnMenu = $("<div/>").appendTo("body");
columnMenu.menu({
onClick: function (item) {
if (item.iconCls == "icon-checkmark") {
grid.datagrid("hideColumn", item.name);
columnMenu.menu("setIcon", { target: item.target, iconCls: "icon-checknomark" });
} else {
grid.datagrid("showColumn", item.name);
columnMenu.menu("setIcon", { target: item.target, iconCls: "icon-checkmark" });
}
}
});
var fields = grid.datagrid("getColumnFields");
for (var i = 0; i < fields.length; i++) {
var field = fields[i];
var col = grid.datagrid("getColumnOption", field);
columnMenu.menu("appendItem", { text: col.title, name: field, iconCls: col.hidden ? "icon-checknomark" : "icon-checkmark" });
}
} //param的部分属性与后台要求不符,重置属性并删除原有属性
function beforeLoad(param) {
if (param.page) {
param.pageIndex = param.page;
delete param.page;
}
if (param.rows) {
param.pageSize = param.rows;
delete param.rows;
}
if (param.sort) {
var array = param.sort.split(',');
for (var i = 0; i < array.length; i++) {
var field = array[i];
array[i] = replaceSearchField(field);
}
param.sort = $.osharp.tools.array.expandAndToString(array, ",");
param.sortField = param.sort;
delete param.sort;
}
if (param.order) {
param.sortOrder = param.order;
delete param.order;
}
if (param.filterRules) {
if (param.filterRules != "[]") {
param.where = getFilterGroup(param.filterRules);
}
delete param.filterRules;
}
} function getFilterGroup(filterRules) {
var group = new $.osharp.filter.group();
var rules = eval(filterRules);
for (var i = 0; i < rules.length; i++) {
var rule = rules[i];
rule.field = replaceSearchField(rule.field);
rule.op = rule.op == "beginwith" ? "startswith" : rule.op == "endwith" ? "endswith" : rule.op; group.Rules.push(new $.osharp.filter.rule(rule.field, rule.value, rule.op));
}
return JSON.stringify(group);
} function loadFilter(data) {
if (data.Type != undefined && data.Type == "Error") {
$.osharp.easyui.msg.error(data.Content);
data.rows = [];
data.total = 0;
return data;
}
if (data.Rows != undefined && data.Total != undefined) {
data.rows = data.Rows;
data.total = data.Total;
delete data.Rows;
delete data.Total;
}
return data;
} function loadError() {
$.osharp.easyui.msg.error("远程数据载入失败,请重试或检查参数。");
} </script>
@RenderSection("endScript", false)
}
@RenderBody()
@RenderSection("headHtml", false)
<div id="grid-@ViewBag.GridId"></div>
@RenderSection("footHtml", false)

实例应用

  OSharp.Web组件中,定义了一个专用于返回表格数据的类,表格只需要行数据与总行数

 /// <summary>
/// 列表数据,封装列表的行数据与总记录数
/// </summary>
/// <typeparam name="T"></typeparam>
public class GridData<T>
{
public GridData()
: this(new List<T>(), )
{ } public GridData(IEnumerable<T> rows, int total)
{
Rows = rows;
Total = total;
} /// <summary>
/// 获取或设置 行数据
/// </summary>
public IEnumerable<T> Rows { get; set; } /// <summary>
/// 获取或设置 数据行数
/// </summary>
public int Total { get; set; }
}

  通过这个类,就可以向easyui返回数据了,如下:

 [AjaxOnly]
public ActionResult GridData()
{
List<object>data =new List<object>();
for (int i = ; i <= ; i++)
{
var item = new { Id = i, Name = "UserName" + i, NickName = "用户" + i, IsDeleted = false, CreatedTime = DateTime.Now.AddMinutes(i) };
data.Add(item);
}
return Json(new GridData<object>(data, data.Count), JsonRequestBehavior.AllowGet);
}

  有了前面定义的 datagrid 父视图 _DataGridLayout.cshtml,用户列表(Views\Users\Index.cshtml)的代码就是如此的简单,仅仅需要把columns重新赋值而已

 @{
ViewBag.Title = "用户信息列表";
Layout = "~/Areas/Admin/Views/Shared/_DataGridLayout.cshtml"; ViewBag.GridId = "users";
ViewBag.GridDataUrl = Url.Action("GridData");
}
@section customScript
{
<script type="text/javascript">
columns = [[
{ field: "Id", title: "编号", width: 40, halign: "center", align: "right", sortable: true },
{ field: "Name", title: "用户名", width: 150, sortable: true },
{ field: "NickName", title: "用户昵称", width: 150, sortable: true },
{ field: "IsDeleted", title: "已删除", width: 80, sortable: true, align: "center", formatter: formatBoolean },
{ field: "CreatedTime", title: "创建时间", width: 150, halign: "center", align: "center", sortable: true, formatter: function (value) { return $.osharp.tools.formatDate(value); } }
]];
</script>
}

  这样,便可以运行出用户列表的结果,如下

  【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构

  比如添加一个角色信息列表,视图(Views\Roles\Index.cshtml)也同用户列表一样,简单到极致:

 @{
ViewBag.Title = "角色信息列表";
Layout = "~/Areas/Admin/Views/Shared/_DataGridLayout.cshtml"; ViewBag.GridId = "roles";
ViewBag.GridDataUrl = Url.Action("GridData");
}
@section customScript
{
<script type="text/javascript">
columns = [[
{ field: "Id", title: "编号", width: 40, halign: "center", align: "right", sortable: true },
{ field: "Name", title: "角色名", width: 150, sortable: true },
{ field: "Remark", title: "角色描述", width: 150, sortable: true },
{ field: "CreatedTime", title: "创建时间", width: 150, halign: "center", align: "center", sortable: true, formatter: function (value) { return $.osharp.tools.formatDate(value); } }
]];
</script>
}

  运行效果:

  【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构

  就是这样,多动脑,多总结,前端的代码也同样能像后台C#代码一样重构,重构到极致。

  未完待续。。。

开源说明

github.com

  OSharp项目已在github.com上开源,地址为:https://github.com/i66soft/osharp,欢迎阅读代码,欢迎 Fork,如果您认同 OSharp 项目的思想,欢迎参与 OSharp 项目的开发。

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

  【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构

nuget

  OSharp的相关类库已经发布到nuget上,欢迎试用,直接在nuget上搜索 “osharp” 关键字即可找到
  【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构

系列导航

本文已同步到系列目录:OSharp快速开发框架解说系列

【开源】OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构的更多相关文章

  1. 【开源】OSharp框架解说系列(1):总体设计及系列导航

    系列文章导航 [开源]OSharp框架解说系列(1):总体设计 [开源]OSharp框架解说系列(2.1):EasyUI的后台界面搭建及极致重构 [开源]OSharp框架解说系列(2.2):EasyU ...

  2. 【开源】OSharp框架解说系列(2&period;2):EasyUI复杂布局及数据操作

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

  3. 【开源】OSharp框架解说系列(6&period;1):日志系统设计

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

  4. 【开源】OSharp框架解说系列(5&period;2):EntityFramework数据层实现

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

  5. 【开源】OSharp框架解说系列(5&period;1):EntityFramework数据层设计

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

  6. 【开源】OSharp框架解说系列(4):架构分层及IoC

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

  7. 【开源】OSharp框架解说系列(3):扩展方法

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

  8. EasyUI的后台界面

    EasyUI的后台界面搭建及极致重构 〇.前言 要了解一个东西长什么样,至少得让我们能看到,才能提出针对性的见解.所以,为了言之有物,而不是凭空漫谈,我们先从UI说起,后台管理页面的UI我们将使用应用 ...

  9. 【开源】OSharp3&period;0框架解说系列(6&period;2):操作日志与数据日志

    OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...

随机推荐

  1. 杂项之python描述符协议

    杂项之python描述符协议 本节内容 由来 描述符协议概念 类的静态方法及类方法实现原理 类作为装饰器使用 1. 由来 闲来无事去看了看django中的内置分页方法,发现里面用到了类作为装饰器来使用 ...

  2. python 根据现有文件树创建文件树

    # -*- coding: utf-8 -*- import os, errno def fileName(path):#获取文件夹 str = '' for i in range(1,len(pat ...

  3. android&colon;descendantFocusability&equals;”blocksDescendants”的用法

    android:descendantFocusability用法简析 开发中很常见的一个问题,项目中的listview不仅仅是简单的文字,常常需要自己定义listview,自己的Adapter去继承B ...

  4. 把vector中的string对象导入到字符指针数组中

    #include <iostream>#include <string>#include <vector>//#include <cctype>#inc ...

  5. hdoj 1010 Tempter of the Bone【dfs查找能否在规定步数时从起点到达终点】【奇偶剪枝】

    Tempter of the Bone Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Othe ...

  6. Java8新特性第2章&lpar;接口默认方法&rpar;

    在Java中一个接口一旦发布就已经被定型,除非我们能够一次性的更新所有该接口的实现,否者在接口的添加新方法将会破坏现有接口的实现.默认方法就是为了解决这一问题的,这样接口在发布之后依然能够继续演化. ...

  7. 图像检索&lpar;3&rpar;&colon;BoW实现

    在上一篇文章中图像检索(2):均值聚类-构建BoF中,简略的介绍了基于sift特征点的BoW模型的构建,以及基于轻量级开源库vlfeat的一个简单实现. 本文重新梳理了一下BoW模型,并给出不同的实现 ...

  8. 华为mate10 UA

    Dalvik/2.1.0 (Linux; U; Android 9; ALP-AL00 Build/HUAWEIALP-AL00) "user-agent": "Mozi ...

  9. 事务的ACID性质

    最近在读黄健宏的<Redis设计与实现>,现在看到了事务这章,由于之前(上学)没有好好整理过数据库事务的四大性质,导致现在(工作)看到了就和第一次知道一样((lll¬ω¬)).还是要把基础 ...

  10. spark快速上手

    spark快速上手 前言 基于Spark 2.1版本 仅仅是快速上手,没有深究细节 主要参考是官方文档 代码均为官方文档中代码,语言为Scala 进入spark-shell 终端输入spark-she ...