标签:
mvc的模板特别类似设计模式中模板方法模式,结合Layout中RenderSection和RenderBody方法可以将部分html展现逻辑延迟到具体的视图页面去实现里面实现。结合我们增删改查的逻辑,我们的用户界面,我们将页面分为这几个区域,实现部分逻辑以后,部分留给具体的页面去实现。例如图片中新增,编辑,删除,导入,导出,查询都是架构自带的操作,至于复制就给页面扩展,查询条件也留给具体的页面中扩展,模板中给出RenderSection即可。
这个执行顺序的问题可以通过调试去查找答案,大致是返回页面后,先执行具体页面的逻辑,当第一个@{} 代码段执行完毕之后,会检查有没有Layout页面,如果有则进入Layout顺序执行,如果Layout页面有RenderSection则会去字页面检查有没有定义,如果定义则执行,页面中除了RenderSection部分都会当作RenderBody中执行,但是实际程序运行中,页面会全部动态编译成dll去执行。具体可以查看Razor引擎模板的相关内容。
mvc定义了一套元数据机制,在模板中可以通过ViewData.ModelMatedata去访问这样可以搭配attribute做出很多扩展的设计,具体案例会在本文后面做详细说明。
@using Coralcode.Framework.Mvc.Models.MiniUI @using HCC.Web.Utils @{ Layout = null; //如果不使用父类默认设定操作按钮,请重新制定参数 if (ViewBag.DataOperation == null) { ViewBag.DataOperation = MiniUIDataOperation.Add | MiniUIDataOperation.Edit | MiniUIDataOperation.Delete | MiniUIDataOperation.ExportTemplate | MiniUIDataOperation.Import | MiniUIDataOperation.Export; } MiniUIDataOperation dataOperation = (MiniUIDataOperation)ViewBag.DataOperation; if (ViewBag.AddEditDialogHeight == null) { ViewBag.AddEditDialogHeight = 600; } if (ViewBag.AddEditDialogWidth == null) { ViewBag.AddEditDialogWidth = 800; } string refreshUrl=ViewBag.ListUrl; if (ViewBag.ShowPager) { refreshUrl = ViewBag.PageUrl; } } <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title></title> <meta http-equiv="content-type" content="text/Html; charset=UTF-8" /> @Styles.Render("~/content/miniuicss") @Scripts.Render("~/script/miniuijs", "~/script/tooljs") </head> <body> <style type="text/css"> html, body { margin: 0; padding: 0; border: 0; width: 100%; height: 100%; overflow: hidden; } </style> <div id="datagrid_toolbar"> <div class="mini-toolbar" style="border-bottom: 0; padding: 0px;"> <table style="width: 100%;"> <tr> <td style="width: 100%;"> <div id="operation"> @if ((dataOperation & MiniUIDataOperation.Add) != 0) { <a class="mini-button" iconcls="icon-add" onclick="add()">增加</a> } @if ((dataOperation & MiniUIDataOperation.Edit) != 0) { <a class="mini-button" iconcls="icon-edit" onclick="edit()">编辑</a> } @if ((dataOperation & MiniUIDataOperation.Delete) != 0) { <a class="mini-button" iconcls="icon-remove" onclick="remove()">删除</a> } @if ((dataOperation & MiniUIDataOperation.ExportTemplate) != 0) { <a class="mini-button" iconcls="icon-download" href=‘@ViewBag.ExportTemplateUrl‘>导出模板</a> } @if ((dataOperation & MiniUIDataOperation.Import) != 0) { <a class="mini-button" iconcls="icon-upload" onclick="importAction()">导入</a> } @if ((dataOperation & MiniUIDataOperation.Export) != 0) { <a class="mini-button" iconcls="icon-download" href=‘@ViewBag.ExportUrl‘>导出</a> } @if ((dataOperation & MiniUIDataOperation.Register) != 0) { <a class="mini-button" iconcls="icon-user" onclick="RegisterLesson()">预约</a> } @if ((dataOperation & MiniUIDataOperation.Sign) != 0) { <a class="mini-button" iconcls="icon-tip" onclick=‘SignLesson()‘>签到</a> } @if ((dataOperation & MiniUIDataOperation.InputUser) != 0) { string currentMember = string.Empty; var userIdName = UserHelper.GetCookieIdName(); if (userIdName != null) { currentMember = string.Format("(当前‘{0}‘)",userIdName.Item2); } <a class="mini-button" iconcls="icon-user" id="a_userName" onclick=‘InputUserMemberView()‘>获取会员@{@currentMember}</a> } @RenderSection("ExtendedOperation", false) </div> </td> <td style="white-space: nowrap;"> <div id="query"> @RenderSection("Query", false) <a class="mini-button" iconcls="icon-search" onclick="LoadData()">查询</a> </div> </td> </tr> </table> </div> </div> <!--撑满页面--> <div class="mini-fit"> <div id="datagrid1" class="mini-datagrid" style="width: 100%; height: 100%;" showpager="@ViewBag.ShowPager" idfield="id" multiselect="true"> <div property="columns"> @Html.Partial("_HeaderPartial", (List<DataGridColumn>)ViewBag.Header) </div> </div> </div> @RenderBody() @* ReSharper disable once SyntaxIsNotAllowed *@ <script type="text/javascript"> mini.parse(); var grid = mini.get("datagrid1"); var url = ‘@Html.Raw(ViewBag.EditUrl)‘; var importUrl = ‘@Html.Raw(ViewBag.ImportUrl)‘; var title = ‘@ViewBag.Title‘; grid.set({ url: "@Html.Raw(refreshUrl)", pageSize:20, pageIndex:10, sizeList:[10,20,50,100] }); function LoadData() { var data="{" + $("#query input[name]").map(function() { return $(this).attr("name") + ":" + "‘"+$(this).val()+"‘"; }).get().join(", ")+"}"; grid.load(mini.decode(data,false)); } //todo 这里如果需要查询怎么办 LoadData(); var dynamicUrlParams = ""; @if ((dataOperation & MiniUIDataOperation.Add) != 0) { <text> function add() { mini.open({ url: url, title: "新增" + title, width: @ViewBag.AddEditDialogWidth, height: @ViewBag.AddEditDialogHeight, onload: function() { var iframe = this.getIFrameEl(); var data = { action: "new" }; //iframe.contentWindow.SetData(data); }, ondestroy: function(action) { grid.reload(); } }); } </text> } @if ((dataOperation & MiniUIDataOperation.Edit) != 0) { <text> function edit() { var row = grid.getSelected(); if (row) { var editUrl = url + (url.indexOf(‘?‘) > 0 ? ‘&‘ : ‘?‘) + "id=" + row.Id; mini.open({ url: editUrl, title: "编辑" + title, width: @ViewBag.AddEditDialogWidth, height: @ViewBag.AddEditDialogHeight, ondestroy: function(action) { grid.reload(); } }); } else { alert("请选中一条记录"); } } </text> } @if ((dataOperation & MiniUIDataOperation.Delete) != 0) { <text> function remove() { var rows = grid.getSelecteds(); if (rows.length > 0) { if (confirm("确定删除选中记录?")) { var ids = []; for (var i = 0, l = rows.length; i < l; i++) { var r = rows[i]; ids.push(r.Id); } var id = ids.join(‘,‘); grid.loading("操作中,请稍后......"); var deleteUrl = ‘@Html.Raw(ViewBag.DeleteUrl)‘; deleteUrl = deleteUrl + (deleteUrl.indexOf(‘?‘) > 0 ? ‘&‘ : ‘?‘) + "ids=" + id; $.ajax({ type: ‘post‘, url: deleteUrl, success: function(data) { if (data.State == 0) { grid.reload(); return; } grid.unmask(); alert(data.Data); }, error: function() { } }); } } else { alert("请选中一条记录"); } } </text> } @if ((dataOperation & MiniUIDataOperation.Import) != 0) { <text> function importAction() { mini.open({ url: importUrl, title: "导入" + title, width: 600, height: 300, ondestroy: function(action) { grid.reload(); } }); } </text> } @if ((dataOperation & MiniUIDataOperation.Register) != 0) { <text> function RegisterLesson() { var rows = grid.getSelecteds(); if (rows.length < 1) { alert("请选中一条记录"); return; } if (rows.length > 1) { alert("每次只能预订一节课程"); return; } var ids = []; for (var i = 0, l = rows.length; i < l; i++) { var r = rows[i]; ids.push(r.Id); } var id = ids.join(‘,‘); grid.loading("操作中,请稍后......"); var registerLessonUrl = ‘@Html.Raw(ViewBag.RegisterLessonUrl)‘; registerLessonUrl = registerLessonUrl + (registerLessonUrl.indexOf(‘?‘) > 0 ? ‘&‘ : ‘?‘) + "ids=" + id + "&"+dynamicUrlParams; $.ajax({ type: ‘post‘, url: registerLessonUrl, success: function (data) { if (data.State == 0) { grid.reload(); return; } grid.unmask(); alert(data.Data); }, error: function () { } }); } </text> } @if ((dataOperation & MiniUIDataOperation.Sign) != 0) { <text> </text> } @if ((dataOperation & MiniUIDataOperation.InputUser) != 0) { <text> function InputUserMemberView(){ mini.prompt("请输入会员号码:", "请输入", function (action, value) { if (action != "ok") { return; } var userLoginUrl = ‘/Portal/User/Login?number=‘+value+ "&"+dynamicUrlParams; $.ajax({ type: ‘post‘, url: userLoginUrl, success: function (data) { if (data.State == 0) { $("#a_userName").children("span").html("获取会员(当前‘"+data.Data+"‘)") return; } alert(data.Data); }, error: function () { } }); } ); } </text> } function onDateRenderer(e) { var value = e.value; if (value) return mini.formatDate(value, ‘yyyy-MM-dd‘); return ""; } function onDateTimeRenderer(e) { var value = e.value; if (value) return mini.formatDate(value, ‘yyyy-MM-dd hh:mm:dd‘); return ""; } function onTimeRenderer(e) { var value = e.value; if (value) return mini.formatDate(value, ‘hh:mm:dd‘); return ""; } </script> @RenderSection("Script", false) </body> </html>
MVC的模板加载顺序,基于mvc的模板顺序,我们可以在自模板中定义出页面操作,如果子页面没有定义,则在模板中给出默认定义。
Flags的枚举 由于页面操作是可以交叉的,所以我们采用flags的枚举来作为判断依据,flags枚举其实就是二进制的位操作,建议没用过的好好去研究下。在很多地方都很有用,特别是条件组合的情况下。
结合RennderSection的设计,模板并不知道操作和查询条件有多少,所以我们做成流式布局,操作左对齐,查询右对齐,在少数操作和少数查询的时候效果还不错,
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="viewport" /> <title></title> <meta http-equiv="content-type" content="text/Html; charset=UTF-8" /> <script type="text/javascript" src="@Url.Content("~/Content/js/jquery-1.6.2.min.js")"></script> <script type="text/javascript" src="@Url.Content("~/Content/js/boot.js")"></script> @Styles.Render("~/content/miniuicss") @Scripts.Render("~/script/miniuijs") @Scripts.Render("~/script/tooljs") <link href="@Url.Content("~/Content/css/edit.css")" type="text/css" rel="stylesheet"/> <script type="text/javascript" src="@Url.Content("~/Content/js/jquery.form.js")"></script> </head> <body style="margin: 0px;height: 100%"> <form method="post" id="formmain" style="width: 100%; height: 100%"> <div class="mini-fit" style="padding: 5px"> <table style="table-layout: fixed; width: 100%;"> @RenderBody() </table> </div> <div class="mini-toolbar" style="text-align:center;padding-top:8px;padding-bottom:8px; " borderStyle="border:1;"> <a id="submit" class="mini-button" iconcls="icon-ok" onclick="submit()">确定</a> <span style="display: inline-block; width: 25px;"></span> <a id="cancel" class="mini-button" iconcls="icon-cancel" onclick="onCancel(this)">取消</a> </div> </form> @Scripts.Render("~/script/jquery.validate") <script type="text/javascript"> function submit() { $.ajax({ type: "Post", data:$("form").serialize(), success: function (jsonResult) { if (jsonResult.State === 0) { CloseWindow("save"); return; } alert(jsonResult.Data); } }); }; function onCancel(e) { CloseWindow("cancel"); } function CloseWindow(action) { if (window.CloseOwnerWindow) window.CloseOwnerWindow(action); else window.close(); } </script> @RenderSection("Script", false) </body> </html>
编辑模板操作ajax提交的方式,将提交逻辑封装在模板页中,子页面只需要填充输入字段即可这里要特别提醒,尽量做到编辑界面的简洁,将那些复杂的输入作为编辑模板扩展,这部分将在下一节中说明。
布局中,将操作放在最下方提供提交和取消两个操作即可满足要求。剩下与输入相关的部分可以在具体页面中去做扩展。最终我们是要实现Html.EditForModel的扩展,暂时精力有限没有做到这一步,最后在更精简的设计中看是否有时间去实现这个逻辑。当然由于部分页面输入逻辑比较复杂,所以即使做出来了,也不能替代全部的编辑页面。
我们查看mvc的源代码可以发现,mvc结合DataType提供了编辑和显示两种模板类型。放在Views/Shared的EditTemplate和DisplayTemplate中,同时在模板中也可以获取到对应模型属性的元数据,这里要特别注意,不要给html.textbox这类输入方法中提供name属性,mvc自己会在name中默认拼接上元数据中的属性名。掌握这种方式之后可以做在编辑页面做出更简洁的设计,如果你扩展的类型够多,那么将会给程序员编程提供很大的便利.
我们选用DataType.MultilineText作为富文本的扩展,将ViewModel属性标记[DataType(DataType.MultilineText)],然后再编辑的时候只需Html.EditFor()即可实现自动调用,我们选用UEditer作为复文本框,只需简单几行代码就可以实现富文本框的扩展。
@model string @Scripts.Render("~/script/ueditor") @Styles.Render("~/content/ueditor") @Html.TextArea("", Model, new { id = ViewData.ModelMetadata.PropertyName }) <script type="text/javascript"> var editor = new baidu.editor.ui.Editor(); editor.render(‘@ViewData.ModelMetadata.PropertyName‘); </script>
在mvc的DataType的枚举中有DateTime,Date,Time三种和时间相关的类型,这里用mini ui自带的时间控件做的扩展,使用的时候只需要结合属性的DateTypeAttribute和
Html.EditFor即可。
DateTime扩展
@model DateTime @{ DateTime value = DateTime.Now; if (Model != DateTime.MinValue) { value=Model ; } } @Html.TextBox("", value, new { @class = "mini-datepicker", format = "yyyy-MM-dd H:mm:ss", timeFormat = "H:mm:ss", showTime = "true" ,width=165})
Date扩展
@model DateTime @{ DateTime value = DateTime.Now; if (Model != DateTime.MinValue) { value = Model; } } @Html.TextBox("", value, new { @class = "mini-datepicker", format = "yyyy-MM-dd", showTime = "false" })
Time扩展
@model DateTime @{ DateTime value = DateTime.Now; if (Model != DateTime.MinValue) { value = Model; } } @Html.TextBox(ViewData.ModelMetadata.PropertyName, value, new { @class = "mini-timespinner", format = "H:mm:ss" })
首先看看整体的压面布局,分为顶栏,左边菜单,右边内容,底部关于这种布局。上边部分就很简单,给出用户信息和登出操作就好了。这里我使用miniui自带的树形控件做的菜单,结合样式miniui自带样式就可以简洁的实现如图的效果
菜单加载就用miniui的tree就很方便了,用Identify和ParentId作为父子关系即可,其中parent属性忽略掉。
var tree = mini.get("leftTree"); tree.loadList(data.Data, "Identify", "ParentId"); 两行js语句即可 public class MenuModel : IViewModel { /// <summary> /// 标识 /// </summary> public string Identify { get; set; } /// <summary> /// 标题 /// </summary> public string Text { get; set; } /// <summary> /// 父节点 /// </summary> [JsonIgnore] public MenuModel Parent { get; set; } /// <summary> /// 父节点标识 /// </summary> public string ParentId { get; set; } /// <summary> /// 链接 /// </summary> public string Url { get; set; } /// <summary> /// 等级 /// </summary> public int Level { get { if (Parent == null) return 0; return Parent.Level + 1; } } /// <summary> /// 子节点 /// </summary> public List<MenuModel> Children { get; set; } public long Id { get; set; } }
using System.Collections.Generic; using HCC.Core.ViewModel; namespace HCC.Core.Services { public interface IMenuService { /// <summary> /// 根节点 /// </summary> MenuModel Root { get; } /// <summary> /// 菜单注册 /// </summary> /// <param name="menuName">菜单名称</param> /// <param name="url">页面地址</param> /// <param name="parentId">上级菜单</param> /// <param name="menuId">菜单Id</param> MenuModel Regist(string menuName, string url, string parentId, string menuId); /// <summary> /// 菜单注册 /// </summary> /// <param name="menuName">菜单名称</param> /// <param name="url">页面地址</param> /// <param name="parentId">上级菜单</param> /// <param name="menuId">菜单Id</param> MenuModel Regist(string menuName, string url, string parentId); /// <summary> /// 注销菜单 /// </summary> /// <param name="menuId"></param> /// <returns></returns> MenuModel Unregist(string menuId); /// <summary> /// 根据菜单ID获取菜单 /// </summary> /// <param name="menuId"></param> /// <returns></returns> MenuModel FindById(string menuId); /// <summary> /// 获取子菜单 /// </summary> /// <returns></returns> List<MenuModel> GetChildrenMenus(string parentId); /// <summary> /// 根据级别获取菜单 /// </summary> /// <returns></returns> List<MenuModel> GetByLevel(List<int> levels); /// <summary> /// 获取全部菜单 /// </summary> /// <returns></returns> List<MenuModel> GetAll(); /// <summary> /// 重新注册菜单 /// </summary> void ResetMenu(); } }
注意其中菜单服务这里,可以订阅eventbus事件来动态更新菜单
using System.Collections.Generic; using System.Linq; using Coralcode.Framework.Aspect; using Coralcode.Framework.Data.Repository.Core; using Coralcode.Framework.Data.Specification; using Coralcode.Framework.Mapper; using Coralcode.Framework.MessageBus.Event; using Coralcode.Framework.Services; using Coralcode.Framework.Utils; using HCC.Core.Model; using HCC.Core.ViewModel; namespace HCC.Core.Services.Imp { [Inject(RegisterType = typeof (IMenuService), LifetimeManagerType = LifetimeManagerType.ContainerControlled)] public class MenuService : CrudCoralService<Menu, MenuModel>, IMenuService { internal static Dictionary<string, MenuModel> MenuCache = new Dictionary<string, MenuModel>(); public MenuService(IRepository<Menu> repository, IEventBus eventBus) : base(repository, eventBus) { Root = new MenuModel { Identify = "root", Text = "后台管理", Url = "", Children = new List<MenuModel>(), Parent = null }; MenuCache.Add(Root.Identify, Root); var allMenus = Repository.GetAll().ToList(); allMenus.ForEach(item => { Regist(item.Text, item.Url, item.ParentId, item.Identify); }); } /// <summary> /// 根节点 /// </summary> public MenuModel Root { get; private set; } public MenuModel Regist(string menuName, string url, string parentId, string menuId) { if (MenuCache.ContainsKey(menuId)) return MenuCache[menuId]; if (string.IsNullOrEmpty(parentId) || !MenuCache.ContainsKey(parentId)) return null; var menu = new MenuModel { Identify = menuId, Text = menuName, Url = url, Children = new List<MenuModel>(), ParentId = parentId, Parent = MenuCache[parentId] }; AddMenu(menu); MenuCache.Add(menu.Identify, menu); MenuCache[parentId].Children.Add(menu); return menu; } public MenuModel Regist(string menuName, string url, string parentId) { return Regist(menuName, url, parentId, GeneralMenuId(parentId, menuName)); } public MenuModel Unregist(string menuId) { MenuModel model; if (!MenuCache.TryGetValue(menuId, out model)) return null; model.Parent.Children.Remove(model); UnregistChildren(model); Repository.UnitOfWork.Commit(); return model; } public MenuModel FindById(string menuId) { return !MenuCache.ContainsKey(menuId) ? null : MenuCache[menuId]; } public List<MenuModel> GetChildrenMenus(string parentId) { if (string.IsNullOrEmpty(parentId)) return DataMapperProvider.Mapper.Convert<List<MenuModel>, List<MenuModel>>(Root.Children); if (MenuCache.ContainsKey(parentId)) return DataMapperProvider.Mapper.Convert<List<MenuModel>, List<MenuModel>>(MenuCache[parentId].Children); return null; } public List<MenuModel> GetByLevel(List<int> levels) { return MenuCache.Values.Where(item => levels.Contains(item.Level)).ToList(); } /// <summary> /// 重新注册菜单 /// </summary> public void ResetMenu() { //删掉之前的菜单 var menus = Repository.GetAll().ToList(); if (menus.Count != 0) { menus.ForEach(item => { Repository.Remove(item); }); Repository.UnitOfWork.CommitAndRefreshChanges(); MenuCache.Clear(); MenuCache.Add(Root.Identify, Root); } //课程管理 var lessonMenu = Regist("课程管理", "", "root", "lessonmanager"); Regist("私教课", "/portal/privatelesson/index", lessonMenu.Identify, "privatelesson"); Regist("公开课", "/portal/publiclesson/index", lessonMenu.Identify, "publiclesson"); Regist("体验课", "/portal/triallesson/index", lessonMenu.Identify, "triallesson"); Regist("会员活动", "/portal/useractivity/index", lessonMenu.Identify, "useractivity"); //人员管理 var userManagerMenu = Regist("人员管理", "", "root", "personmanager"); Regist("教练", "/portal/coach/index", userManagerMenu.Identify, "coachmanager"); Regist("人员", "/portal/user/index", userManagerMenu.Identify, "usermanager"); //系统管理 var systemManagerMenu = Regist("系统管理", "", "root", "systemmanager"); Regist("管理员", "/portal/manager/index", systemManagerMenu.Identify, "manager"); Regist("新闻管理", "/portal/news/index", systemManagerMenu.Identify, "news"); Regist("留言管理", "/portal/message/index", systemManagerMenu.Identify, "message"); Regist("课程模板", "/portal/lessontemplate/index", systemManagerMenu.Identify, "lessontemplate"); Regist("系统设置", "/portal/systemsetting/index", systemManagerMenu.Identify, "systemsetting"); //统计查询 var stasticsMenu = Regist("统计", "", "root", "systemstastistics"); Regist("统计", "/portal/statistic/index", stasticsMenu.Identify, "stastistics"); } private void UnregistChildren(MenuModel menu) { menu.Children.ForEach(item => { if (MenuCache.ContainsKey(item.Identify)) RemoveMenu(item); UnregistChildren(item); }); RemoveMenu(menu); } private void AddMenu(MenuModel menu) { if (MenuCache.ContainsKey(menu.Identify)) return; var model = Repository.GetFirst(new DirectSpecification<Menu>(item => item.Identify == menu.Identify)); if (model != null) return; Repository.Add(Convert(menu)); Repository.UnitOfWork.Commit(); } private void RemoveMenu(MenuModel menu) { if (MenuCache.ContainsKey(menu.Identify)) MenuCache.Remove(menu.Identify); var model = Repository.GetFirst(new DirectSpecification<Menu>(item => item.Identify == menu.Identify)); if (model == null) return; Repository.Remove(model); } private string GeneralMenuId(params string[] keys) { return StringUtil.FormatKey(keys.ToArray()); } } }
菜单缓存采用和两种模式一种是根节点的树形模式,一种是字段缓存,这样既可以从树
去访问,也可以从字典根据key去访问。
Ps:界面代码比较乱,后面放出demo时候再整理,快了,由于最近确实比较忙,更新比较慢,见谅
标签:
原文地址:http://www.cnblogs.com/Skyven/p/5674865.html