你相信么,使用PasteForm框架开发,管理端居然不要写代码!!!
一起来看看PasteForm是否支持多级表模式(外表)
需求假设
假如有这么一个需求,就是订单表,包含了多级的信息,比如这个订单包含了哪些商品,又有哪些货品信息等,以下是一个概览,模拟的是实际的一些层级关系
Entity定义
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Domain.Entities;namespace LevelEntity.levelmodels
{/// <summary>/// /// </summary>public class OrderDetail : Entity<int>{/// <summary>/// 标题/// </summary>[MaxLength(128)]public string Title { get; set; }/// <summary>/// 封面图/// </summary>[MaxLength(128)]public string CoverImage { get; set; }/// <summary>/// 订单包含产品/// </summary>public ICollection<OrderProduct> Products { get; set; }/// <summary>/// 订单金额/// </summary>public OrderPrice Price { get; set; }}/// <summary>/// /// </summary>public class OrderProduct : Entity<int>{/// <summary>/// /// </summary>public int ProductId { get; set; }/// <summary>/// /// </summary>public int BuyNum { get; set; }/// <summary>/// /// </summary>public int ProductPrice { get; set; }/// <summary>/// 货品信息/// </summary>public SkuInfo Sku { get; set; }}/// <summary>/// /// </summary>public class SkuInfo : Entity<int>{/// <summary>/// 货品名称/// </summary>[MaxLength(32)]public string SkuName { get; set; }}/// <summary>/// /// </summary>public class OrderPrice : Entity<int>{/// <summary>/// 订单总金额/// </summary>public int TotalAmount { get; set; }/// <summary>/// 优惠总金额/// </summary>public int FreeAmount { get; set; }}/// <summary>/// 购买者信息/// </summary>public class OrderBuyer : Entity<int>{/// <summary>/// 姓名/// </summary>[MaxLength(16)]public string Name { get; set; }/// <summary>/// 年龄/// </summary>public int Age { get; set; }}
}
如上所示,我把他们全部放于XXX.Domain/levelmodels的文件夹的OrderDetail.cs中
结构如下
确认了字段的注释等之后,右键OrderDetail.cs这个文件,使用PasteBuidler构建代码
可以生成对应的Dto和AppService模块
如上图所示,会在这些地方生成对应的代码,自动生成的代码有些需要调整,我们进行针对性的调整下
调整Dto
我们以新增OrderDetail为例子,对OrderDetailAddDto调整如下
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using LevelEntity.Application.Contracts;
using PasteFormHelper;namespace LevelEntity.levelmodels
{///<summary>//////</summary>public class OrderDetailAddDto{///<summary>///标题///</summary>[MaxLength(128)]public string Title { get; set; }///<summary>///封面图///</summary>[MaxLength(128)]public string CoverImage { get; set; }///<summary>///包含产品 这里会自动构建组,因为是list和directsun///</summary>[PasteDirectsun]public List<OrderProductUpdateDto> Products { get; set; }///<summary>///订单金额///</summary>[PasteDirectsun]public OrderPriceUpdateDto Price { get; set; }}///<summary>//////</summary>public class OrderDetailUpdateDto : OrderDetailAddDto{/// <summary>/// /// </summary>public int Id { get; set; }/<summary>/标题/</summary>//[MaxLength(128)]//public string Title { get; set; }/<summary>/封面图/</summary>//[MaxLength(128)]//public string CoverImage { get; set; }/<summary>/订单包含产品/</summary>//public List<OrderProductDto> Products { get; set; }/<summary>/订单金额/</summary>//public OrderPriceDto Price { get; set; }}///<summary>//////</summary>public class OrderDetailDto : OrderDetailUpdateDto{/<summary>/标题/</summary>//[MaxLength(128)]//public string Title { get; set; }/<summary>/封面图/</summary>//[MaxLength(128)]//public string CoverImage { get; set; }/<summary>/订单包含产品/</summary>//public List<OrderProductDto> Products { get; set; }/<summary>/订单金额/</summary>//public OrderPriceDto Price { get; set; }}///<summary>//////</summary>public class OrderDetailListDto{/// <summary>/// ID/// </summary>public int Id { get; set; }///<summary>///标题///</summary>[MaxLength(128)][PasteClass]public string Title { get; set; }///<summary>///封面图///</summary>[MaxLength(128)][PasteClass]public string CoverImage { get; set; }///<summary>///订单包含产品///</summary>[PasteHidden]public List<OrderProductDto> Products { get; set; }///<summary>///订单金额///</summary>[PasteDisplay("totalAmount")][PasteFenToYuan]public OrderPriceDto Price { get; set; }}///<summary>/// 查询///</summary>public class InputQueryOrderDetail : InputSearchBase{/// <summary>/// 货品名称/// </summary>public string sku_name { get; set; }/// <summary>/// 商品ID/// </summary>public int pro_id { get; set; }}
}
注意看OrderDetailAddDto的这个信息
///<summary>///包含产品 这里会自动构建组,因为是list和directsun///</summary>[PasteDirectsun]public List<OrderProductUpdateDto> Products { get; set; }///<summary>///订单金额///</summary>[PasteDirectsun]public OrderPriceUpdateDto Price { get; set; }
我偷懒了下,直接用UpdateDto,其实应该用AddDto的,也就是
///<summary>///包含产品 这里会自动构建组,因为是list和directsun///</summary>[PasteDirectsun]public List<OrderProductAddDto> Products { get; set; }///<summary>///订单金额///</summary>[PasteDirectsun]public OrderPriceAddDto Price { get; set; }
不过实际中影响不大,因为我直接把对应的Id隐藏了
比如对应的OrderProductUpdateDto的代码调整如下
///<summary>//////</summary>public class OrderProductUpdateDto{/// <summary>/// /// </summary>[PasteHidden]public int Id { get; set; }///<summary>///商品ID///</summary>public int ProductId { get; set; }///<summary>///购买数量///</summary>public int BuyNum { get; set; }///<summary>///售价///</summary>public int ProductPrice { get; set; }///<summary>///货品信息///</summary>[PasteDirectsun]public SkuInfoUpdateDto Sku { get; set; }}
而SkuInfoUpdateDto的代码修改如下
///<summary>//////</summary>public class SkuInfoUpdateDto{/// <summary>/// /// </summary>[PasteHidden]public int Id { get; set; }///<summary>///货品名称///</summary>[MaxLength(32)]public string SkuName { get; set; }}
Dto解读
由上面的代码可知,主要是在子Object中加入了[PasteDirectSun]
这个其实就是特性directsun的内容,具体的可以查看
https://soft.pastecode.cn/doc/form/classfunc/directsun
(如果没有,就是我文档还没有升级哈!)
至于其他字段就是正常的字段了,不做其他说明,一起来看看如何实现新增和更新
新增
开发过ABP框架项目的都知道,新增走的是AddDto,这里就是OrderDetailAddDto
然后我们看下PasteForm在对于这个新增的时候做了什么操作
一起看下XXX.Application/levelmodels的OrderDetailAppService.cs中的
ReadAddModel
代码如下
/// <summary>/// 读取AddDto的数据模型/// </summary>/// <returns></returns>[HttpGet][TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "add" })]public VoloModelInfo ReadAddModel(){var dto = new OrderDetailAddDto();dto.Products = new List<OrderProductUpdateDto> { new OrderProductUpdateDto() { Sku = new SkuInfoUpdateDto { } } };var _model = PasteBuilderHelper.DynamicReadModelProperty<OrderDetailAddDto>(dto);return _model;}
上面代码中要注意,当设计子对象的时候,需要先new一个对象出来,不然UI中没法解析对象!
估计这个问题后续会改版,就是初始化对象的问题!
这样让返回的数据格式中包含了比如Products的内容,也就是UI知道ProductUpdateDto的格式信息,以及对应的SkuInfoUpdateDto的内容!
注意,我上面解析对象用的反射是PasteBuilderHelper.DynamicReadModelProperty
新增表单
修改完成以上代码之后,我们要做一个add-migration操作
因为修改了数据库表结构等
add-migration init_database -Context SqliteDbContext
由于我本地测试使用的是Sqlite数据库
运行后,需要添加菜单
- pasteform/index.html?path=orderDetail
然后刷新页面后,可以点击新增,可以看到如下
发现没有,字段已经对应上了,上面还有一个分组模块,叫包含产品
这个包含产品后面还有+ -,这个对应的就是Products
!!!注意,如果都点击-,一个都没有保留,那就需要刷新页面重新加载了!
我们多点击+几次,然后输入信息,大概如下
表单效果
点击保存!
理论上是没有错误的,提示成功,然后刷新表格页面!
点击编辑看看数据是否一致!!!
这里数据是否一致,要看查询接口,也就是
/// <summary>/// 读取UpdateDto的数据模型/// </summary>/// <returns></returns>[HttpGet][TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "edit" })]public async Task<VoloModelInfo> ReadUpdateModel(int id){var _info = await _dbContext.OrderDetail.Where(x => x.Id == id).Include(x => x.Products).ThenInclude(op => op.Sku).Include(x => x.Price).AsNoTracking().FirstOrDefaultAsync();if (_info == null || _info == default){throw new PasteCodeException("查询的信息不存在,无法执行编辑操作!");}var dto = ObjectMapper.Map<OrderDetail, OrderDetailUpdateDto>(_info);var _dataModel = PasteBuilderHelper.DynamicReadModelProperty<OrderDetailUpdateDto>(dto);return _dataModel;}
看代码,是不是把Product.sku也包含进来了!!!
表格查询
上面已经例举了新增多层级表单和编辑多层级表单,那么接下来就是查询表格数据,就是按页查询数据,这里演示下包括子集的查询!
查询项配置
我们知道PasteForm框架的查询也是依托于Dto的,默认是InputQueryXXXX
本次这里的就是
///<summary>/// 查询///</summary>public class InputQueryOrderDetail : InputSearchBase{/// <summary>/// 货品名称/// </summary>public string sku_name { get; set; }/// <summary>/// 商品ID/// </summary>public int pro_id { get; set; }}
在默认基础上我加了2个字段查询
然后看看使用的地方
/// <summary>/// 获取/// </summary>/// <param name="input"></param>/// <returns></returns>[HttpGet]public async Task<PagedResultDto<OrderDetailListDto>> Page([FromQuery] InputQueryOrderDetail input){var _query = _dbContext.OrderDetail.Where(t => 1 == 1).Include(x => x.Products).ThenInclude(op => op.Sku).Include(x => x.Price).WhereIf(!string.IsNullOrEmpty(input.word), x => x.Title.Contains(input.word)).WhereIf(input.pro_id != 0, x => x.Products.Any(y => y.ProductId == input.pro_id)).WhereIf(!string.IsNullOrEmpty(input.sku_name), x => x.Products.Any(y => y.Sku != null && y.Sku.SkuName.Contains(input.sku_name)));var _pagedto = new PagedResultDto<OrderDetailListDto>();if (input.page == 1){_pagedto.TotalCount = await _query.CountAsync();}var dataList = await _query.OrderByDescending(x => x.Id).Page(input.page, input.size).AsNoTracking().ToListAsync();if (dataList == null || dataList.Count == 0){throw new PasteCodeException("没有查询到数据", 204);}var temList = ObjectMapper.Map<List<OrderDetail>, List<OrderDetailListDto>>(dataList);_pagedto.Items = temList;return _pagedto;}
一起来看看表格中是怎样的
查询区域
如上所示,多了2个查询项,就是我们在InputQueryOrderDetail中添加的,所以说PasteForm添加查询就是这么简单!
测试下是否会按照输入值查询
对于前端来说就是F12看看提交的查询数据
对于后端来说,就是接受到查询条件,然后执行查询
我这测试OK!
功能回顾
有没有一种感觉,加一个表居然这么简单???
你以为上面漏了啥?实话告诉你,上面的步骤真没有漏!
!!不需要修改管理端代码,就添加了一个多级表的表单实现!!
由以上信息得知,对于多层级的Entity或者有集合的Entity的表单,对于PasteForm框架来说,可以在一个表单中完成!
需要注意的点是关于对象的实例信息,也就是进行反射Model的时候,那些对象的字段需要进行new一个默认的出来,反射才能获得对应的信息!
对于Collection来说,UI会支持±的功能!
上面回顾其实还是有可圈可点的地方
- 1.对于字段是Object的,其实可以在解反射的时候自己new一个出来,然后解析!
- 2.其实上面有一个冲突的地方,就是directsun的地方不能用group特性,这样会覆盖,查看前端代码可知,group要支持±的功能,需要是多层级模式!
- 3.?是否支持->obj->items->obj->items的模式呢?等待你的测试!!!