本文将介绍soft.pastecode.cn出品的PasteForm,PasteForm是贴代码使用Dto思想实现的CRUD的一个组件,或者说输出一个思想!
为啥我觉得是最佳的CRUD呢?先结合你的实际项目解答下以下问题:
1.如果有一个系统,有100个表,你的管理端需要多少页面?别和我说100个表很多,需求复杂点的随随便便上100个数据库表的!
2.新的需求下来了,说XX表要新增一个字段,默认为100,新增的时候输入,不支持修改,作为你目前的管理端你需要几个步骤来实现?关键点你如何做版本过渡的问题?
3.做开发的都是到,表和表之间一般是有关系的,虽然不一定会建立外表链,比如BindUserGrade的表中的UserId一般就是表示UserInfo表的Id,这就牵扯到录入,更新,和显示的问题了,其实我们希望看到的是显示UserInfo中的UserName而不是干巴巴的一个UserId对吧!
4.一个表我们在管理端的时候一般会涉及到数据的列表显示,比较常用的就是table表格对吧,对应的有新增,编辑,查看,删除,那作为管理端你如何控制他们的权限?权限往往涉及2大块,a.前端页面的显示否,b.后端接口中的权限判定
… … .
让我们一起来看下PasteForm的案例项目PasteTemplate是如何处理以上问题的!
案例代码 https://gitee.com/pastecode/paste-template/tree/master/example/PasteTemplate
从上面下载代码后,直接启动,然后,应该是localhost:22222端口 http://localhost:22222/page/index.html
登陆页面
登陆页面没什么好说的,因为不涉及找回密码,注册等,只有图形验证码和登陆
管理端
登陆后,看到如上图
左侧菜单采用动态的模式,菜单由权限读取,系统在首次启动的时候会把默认的菜单写入到数据库,如果当前账号有root-root的权限,则会返回所有的菜单,也就是root-root表示超级权限的意思,拥有系统的所有权限!
由于其他表的信息比较简单,我就从测试表的数据来举例子
页面组成
PasteForm的整个体系其实只有4个页面,
1.上图的index.html页面,作为管理端的菜单页面
2.右侧区域的数据表格table显示和他上方的搜索区域组成的pasteform/index.html页面
3.对应的表的编辑和新增页面,新增和编辑是同一个页面,这里命名为pasteform/view.html,也就是form表单页面
4.有些时候表的数据多大,我们不希望在table中全部显示,比如博文的正文,这就需要有一个页面查看详细的,pasteform/detail.html
index.html
管理端的主页面,大概是左右布局,100%绝对定位,是为了满足右侧子页面的flex布局,主要点是左侧的tree,内容是从API读取的,也就是不同账号登陆后看到的菜单是不一样的,具体的看角色对应的菜单(权限)
pasteform/index.html
如上图,你看到的内容,可以说整个页面都是后端的Dto控制的
1.比如搜索区域有多少个搜索项,搜索项的交互(daterange,date,outer,select,selects,word等)
2.中间区域的表格,包含了新增,编辑,删除,详情的基本4个菜单,然后就是自定义的一些按钮,比如上图的 添加子集!
3.然后是表格区域内的排序,注意看表格header中的ID 排序 层级 其实哪些字段支持排序,在后端的Dto中也就一行代码的事情!
4.表格中的一些交互,比如上图的switch,点击后是可以直接修改的,操作后会和后端API进行交互,如果返回非200则会重置状态
5.按钮区域的提交按钮,有些时候表格的按钮需要条件判断,那也是支持的,比如age>18的显示按钮1,age<12的显示按钮2等!
6.表格的自定义显示,有时候我们希望一个表格显示多个字段,比如换行啥的,也是可以实现的
pasteform/view.html
表单主页面,作为新增或者编辑的页面,支持几乎常见的录入
1.基础录入包括默认值等,比如text,number,switch,select等
2.也支持特殊的格式,比如daterange,richtext,textarea,selects,image,file等
3.高级支持,外表输入,比如选择其他表的一个对象作为这个表单的一个录入
点击父级后面的输入框,是弹出一个页面,选择一个对象,作为这个输入框的值,值一般包含2个,一个是id,一个是显示的
4.高级支持,参数输入,比如权限列表中的添加子集,那么打开的应该是添加页面,这个时候父级是不要输入的,由url参数传递过去
还有更多的功能等你发觉,上面说得一大堆功能,其实都是Dto中配置的
啥叫Dto?
接触过ABPvNext的应该比较数据,其实就是不同的Model,比如UserInfo这个表,一般会创建对应的4个Dto
UserInfoAddDto:作为UserInfo新增的数据模型
UserInfoUpdateDto:作为UserInfo的编辑的数据模型
UserInfoDto:一般作为详细显示的数据模型
UserInfoListDto:一般作为表格,列表的数据展示用
这几个模型可以配置互转,就是使用AutoMapper!
为了实现更加灵活的CRUD,我们只需要在对应的Dto中的字段上添加对应的属性即可,由于需求比较特殊,我定义了一个属性
/// <summary>
/// 前端用数据类型
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = true)]
public class ColumnDataTypeAttribute : Attribute
{
/// <summary>
/// image region navigator select selects dateplan datetime等
/// </summary>
public string Name { get; set; } = "";
/// <summary>
///
/// </summary>
public string Args1 { get; set; } = "";
/// <summary>
///
/// </summary>
public string Args2 { get; set; } = "";
/// <summary>
/// 参数3
/// </summary>
public string Args3 { get; set; } = "";
/// <summary>
/// 参数4
/// </summary>
public string Args4 { get; set; } = "";
/// <summary>
///
/// </summary>
public string ErrorMessage { get; set; } = "";
/// <summary>
/// 按照要求文档填写参数
/// </summary>
/// <param name="name"></param>
/// <param name="args1"></param>
/// <param name="args2"></param>
/// <param name="args3"></param>
/// <param name="args4"></param>
/// <param name="ErrorMessage"></param>
public ColumnDataTypeAttribute(string name = "", string args1 = "", string args2 = "", string args3 = "", string args4 = "", string ErrorMessage = "")
{
this.Name = name;
this.Args3 = args3;
this.Args1 = args1;
this.Args2 = args2;
this.Args4 = args4;
this.ErrorMessage = ErrorMessage;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public override string ToString()
{
return string.Format("{0}-{1}-{2}-{3}-{4}", Name, Args1, Args2, Args3, Args4);
}
}
然后是在需要限定的字段添加这个或者多个这个属性,比如
///<summary>
///测试表 用于测试CURD的表
///</summary>
public class TestTableAddDto
{
///<summary>
///姓名 模拟短文本输入
///</summary>
[MaxLength(32)]
[Required]
[PasteMark("test", "name")]
public string Name { get; set; }
///<summary>
///头像 模拟图片上传
///</summary>
[MaxLength(128)]
[PasteMark("test", "head")]
[ColumnDataType("image", "1", "head", "60*60")]
public string Head { get; set; }
///<summary>
///年龄 模拟输入number
///</summary>
[Range(5, 90, ErrorMessa