博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
CRUD全栈式编程架构之界面层的设计
阅读量:7259 次
发布时间:2019-06-29

本文共 13156 字,大约阅读时间需要 43 分钟。

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")
@if ((dataOperation & MiniUIDataOperation.Add) != 0) {
增加 } @if ((dataOperation & MiniUIDataOperation.Edit) != 0) {
编辑 } @if ((dataOperation & MiniUIDataOperation.Delete) != 0) {
删除 } @if ((dataOperation & MiniUIDataOperation.ExportTemplate) != 0) {
导出模板 } @if ((dataOperation & MiniUIDataOperation.Import) != 0) {
导入 } @if ((dataOperation & MiniUIDataOperation.Export) != 0) {
导出 } @if ((dataOperation & MiniUIDataOperation.Register) != 0) {
预约 } @if ((dataOperation & MiniUIDataOperation.Sign) != 0) {
签到 } @if ((dataOperation & MiniUIDataOperation.InputUser) != 0) { string currentMember = string.Empty; var userIdName = UserHelper.GetCookieIdName(); if (userIdName != null) { currentMember = string.Format("(当前'{0}')",userIdName.Item2); }
获取会员@{@currentMember} } @RenderSection("ExtendedOperation", false)
@RenderSection("Query", false)
查询
@Html.Partial("_HeaderPartial", (List
)ViewBag.Header)
@RenderBody() @* ReSharper disable once SyntaxIsNotAllowed *@ @RenderSection("Script", false)

 

操作配置化,并且可扩展

MVC的模板加载顺序,基于mvc的模板顺序,我们可以在自模板中定义出页面操作,如果子页面没有定义,则在模板中给出默认定义。

Flags的枚举 由于页面操作是可以交叉的,所以我们采用flags的枚举来作为判断依据,flags枚举其实就是二进制的位操作,建议没用过的好好去研究下。在很多地方都很有用,特别是条件组合的情况下。

流式布局

结合RennderSection的设计,模板并不知道操作和查询条件有多少,所以我们做成流式布局,操作左对齐,查询右对齐,在少数操作和少数查询的时候效果还不错,

新增编辑布局页

@{    Layout = null;}    
@Styles.Render("~/content/miniuicss") @Scripts.Render("~/script/miniuijs") @Scripts.Render("~/script/tooljs")
@RenderBody()
@Scripts.Render("~/script/jquery.validate") @RenderSection("Script", false)

 

界面尽量精简

编辑模板操作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 List
Children { 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
, IMenuService { internal static Dictionary
MenuCache = new Dictionary
(); public MenuService(IRepository
repository, IEventBus eventBus) : base(repository, eventBus) { Root = new MenuModel { Identify = "root", Text = "后台管理", Url = "", Children = new List
(), Parent = null }; MenuCache.Add(Root.Identify, Root); var allMenus = Repository.GetAll().ToList(); allMenus.ForEach(item => { Regist(item.Text, item.Url, item.ParentId, item.Identify); }); } ///
/// 根节点 /// 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
(), 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
GetChildrenMenus(string parentId) { if (string.IsNullOrEmpty(parentId)) return DataMapperProvider.Mapper.Convert
, List
>(Root.Children); if (MenuCache.ContainsKey(parentId)) return DataMapperProvider.Mapper.Convert
, List
>(MenuCache[parentId].Children); return null; } public List
GetByLevel(List
levels) { return MenuCache.Values.Where(item => levels.Contains(item.Level)).ToList(); } ///
/// 重新注册菜单 /// 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
(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
(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时候再整理,快了,由于最近确实比较忙,更新比较慢,见谅

 

转载于:https://www.cnblogs.com/Skyven/p/5674865.html

你可能感兴趣的文章
初学Oracle的一点心得
查看>>
远程医疗技术在国内的发展机遇
查看>>
机器视觉照明系统光源该如何选择
查看>>
一个insert插入语句很慢的优化
查看>>
sysbench压测小记(r11笔记第99天)
查看>>
使用awk来解析dump文件
查看>>
SQL Server中提前找到隐式转换提升性能的办法
查看>>
Javascript知识四(DOM)
查看>>
Python验证码识别处理实例(转)
查看>>
Do a “git export” (like “svn export”)?(转)
查看>>
[20170112]为什么提示不一样.txt
查看>>
ThinkPHP连接sql server数据库
查看>>
css优先级计算规则
查看>>
zookeeper使用和原理探究 (注意linux下防火墙导致启动失败的坑,使用service iptables stop 关闭防火墙 使用service iptables status确认)...
查看>>
【数值分析】复化积分公式
查看>>
改善C#程序的建议5:引用类型赋值为null与加速垃圾回收
查看>>
DMR技术白皮书
查看>>
搭建Tomcat
查看>>
如何写一份简单易懂的软件外包需求说明书
查看>>
安全漏洞问题4:跨站请求伪造
查看>>