Layout的设计
模板模式
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; }}@Styles.Render("~/content/miniuicss") @Scripts.Render("~/script/miniuijs", "~/script/tooljs") @RenderBody() @* ReSharper disable once SyntaxIsNotAllowed *@ @RenderSection("Script", false)@Html.Partial("_HeaderPartial", (List)ViewBag.Header)
操作配置化,并且可扩展
MVC的模板加载顺序,基于mvc的模板顺序,我们可以在自模板中定义出页面操作,如果子页面没有定义,则在模板中给出默认定义。
Flags的枚举 由于页面操作是可以交叉的,所以我们采用flags的枚举来作为判断依据,flags枚举其实就是二进制的位操作,建议没用过的好好去研究下。在很多地方都很有用,特别是条件组合的情况下。
流式布局
结合RennderSection的设计,模板并不知道操作和查询条件有多少,所以我们做成流式布局,操作左对齐,查询右对齐,在少数操作和少数查询的时候效果还不错,
新增编辑布局页
@{ Layout = null;}@Scripts.Render("~/script/jquery.validate") @RenderSection("Script", false)@Styles.Render("~/content/miniuicss") @Scripts.Render("~/script/miniuijs") @Scripts.Render("~/script/tooljs")
界面尽量精简
编辑模板操作ajax提交的方式,将提交逻辑封装在模板页中,子页面只需要填充输入字段即可这里要特别提醒,尽量做到编辑界面的简洁,将那些复杂的输入作为编辑模板扩展,这部分将在下一节中说明。
自适应的布局
布局中,将操作放在最下方提供提交和取消两个操作即可满足要求。剩下与输入相关的部分可以在具体页面中去做扩展。最终我们是要实现Html.EditForModel的扩展,暂时精力有限没有做到这一步,最后在更精简的设计中看是否有时间去实现这个逻辑。当然由于部分页面输入逻辑比较复杂,所以即使做出来了,也不能替代全部的编辑页面。
MVC编辑模板
挖掘MVC源代码地图
我们查看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 })
时间相关扩展
在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 { ////// 标识 /// public string Identify { get; set; } ////// 标题 /// public string Text { get; set; } ////// 父节点 /// [JsonIgnore] public MenuModel Parent { get; set; } ////// 父节点标识 /// public string ParentId { get; set; } ////// 链接 /// public string Url { get; set; } ////// 等级 /// public int Level { get { if (Parent == null) return 0; return Parent.Level + 1; } } ////// 子节点 /// public ListChildren { get; set; } public long Id { get; set; } }
菜单的服务
using System.Collections.Generic;using HCC.Core.ViewModel;namespace HCC.Core.Services{ public interface IMenuService { ////// 根节点 /// MenuModel Root { get; } ////// 菜单注册 /// /// 菜单名称 /// 页面地址 /// 上级菜单 /// 菜单Id MenuModel Regist(string menuName, string url, string parentId, string menuId); ////// 菜单注册 /// /// 菜单名称 /// 页面地址 /// 上级菜单 /// 菜单Id MenuModel Regist(string menuName, string url, string parentId); ////// 注销菜单 /// /// ///MenuModel Unregist(string menuId); /// /// 根据菜单ID获取菜单 /// /// ///MenuModel FindById(string menuId); /// /// 获取子菜单 /// ///List GetChildrenMenus(string parentId); /// /// 根据级别获取菜单 /// ///List GetByLevel(List levels); /// /// 获取全部菜单 /// ///List GetAll(); /// /// 重新注册菜单 /// 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
菜单的缓存
菜单缓存采用和两种模式一种是根节点的树形模式,一种是字段缓存,这样既可以从树
去访问,也可以从字典根据key去访问。
Ps:界面代码比较乱,后面放出demo时候再整理,快了,由于最近确实比较忙,更新比较慢,见谅