前言
在开发实际项目中,其实CRUD的代码量并不小,最近要做一个小程序项目,由于涉及表单的东西比较多,就萌生了一个想法,小程序的写法不是和VUE类似,就是数据绑定,模块么!那就来一个动态表单,动态数据!
合理架构
了解ABP框架的,应该比较好理解Dto的好处,就是XXX.Application.Contracts,比如用户表可以引申出几个模型!
UserInfo:用户的原生表,相当于数据库表的结构
UserInfoDto:用户表的详细,一般会做一些数据处理,数据补充,外表的扩展等,比如会显示角色信息,会对密码做脱敏处理等
UserInfoUpdateDto:更新用户表的时候,哪些字段要更新,不要更新的字段注释掉,或者删除!
UserInfoAddDto:新建用户的时候的数据模型,同理不需要的删除或者注释掉!
对外的数据模型一般是Dto,比如上方的Dto都把PassWord这个字段注释掉,那么对外就不会泄漏密码了,然后他们之间使用ObjectMapper进行数据映射转化!
读取XXXDto的属性
如果我们要搞全自动的,那么就要知道Dto的属性,比如这个模型有多少个字段,各叫啥名字,有没有啥限定(最大字符长度,是否必填,默认值等!)
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;namespace PasteTemplate.Application
{/// <summary>/// /// </summary>public static class PasteBuilderHelper{/// <summary>/// 读取某一个模型的规则/// </summary>/// <typeparam name="T"></typeparam>/// <param name="_new"></param>public static VoloModelInfo ReadModelProperty<T>(T _new){var _classModel = new VoloModelInfo();var _classType = typeof(T);var _bases = _classType.GetBaseClasses();if (_bases != null){foreach (var _base in _bases){if (_base.FullName != "System.Object"){if (_base.GenericTypeArguments != null){foreach (var _arg in _base.GenericTypeArguments){switch (_arg.Name){case "Int64":{_classModel.KeyType = "long";}break;case "String":{_classModel.KeyType = "string";}break;case "Guid":{_classModel.KeyType = "Guid";}break;}}}}}}string assemblyPath = Assembly.GetExecutingAssembly().Location;var _path = Path.GetDirectoryName(assemblyPath);var _name = Assembly.GetExecutingAssembly().GetName().Name;XDocument xmlDoc = null;//T: 表示类型 //P: 表示字段var xmlDocumentationPath = $@"{_path}\{_name.Replace(".Application", ".Application.Contracts").Replace("HttpApi.Host", "Application.Contracts")}.xml";if (System.IO.File.Exists(xmlDocumentationPath)){xmlDoc = XDocument.Load(xmlDocumentationPath);XElement typeElement = xmlDoc.Descendants("member").FirstOrDefault(member => member.Attribute("name")?.Value == $"T:{_classType.FullName}");if (typeElement != null){var _classSummary = typeElement.Element("summary")?.Value;if (!string.IsNullOrEmpty(_classSummary)){_classSummary = _classSummary.Replace("\r\n", "").Trim();_classModel.Summary = _classSummary;}}}var _pro_list = new List<VoloModelProperty>();foreach (var _property in _classType.GetProperties()){var _cpro = new VoloModelProperty();if (xmlDoc != null){var _pro_full = $"P:{_classType.FullName}.{_property.Name}";XElement typeElement = xmlDoc.Descendants("member").FirstOrDefault(member => member.Attribute("name")?.Value == _pro_full);if (typeElement != null){var _summary = typeElement.Element("summary")?.Value;if (!String.IsNullOrEmpty(_summary)){_summary = _summary.Replace("\r\n", "").Trim();_cpro.Summary = _summary;}}}_cpro.Name = _property.Name.FirstLetterToLower();_cpro.Type = _property.PropertyType.Name;_cpro.Default = _property.GetValue(_new)?.ToString();var attributes = _property.GetCustomAttributes<Attribute>();var _attributes = new List<VoloModelAttribute>();foreach (var _attribute in attributes){var _attri = new VoloModelAttribute();if (_attribute.GetType() == typeof(MaxLengthAttribute)){var _bute = (MaxLengthAttribute)_attribute;_attri.Name = "MaxLength";_attri.ErrorMessage = _bute.ErrorMessage;_attri.Args1 = _bute.Length.ToString();}if (_attribute.GetType() == typeof(RequiredAttribute)){var _bute = (RequiredAttribute)_attribute;_attri.Name = "Required";}if (_attribute.GetType() == typeof(RangeAttribute)){var _bute = (RangeAttribute)_attribute;_attri.Name = "Range";_attri.Args1 = _bute.Minimum.ToString();_attri.Args2 = _bute.Maximum.ToString();}if (_attribute.GetType() == typeof(RegularExpressionAttribute)){var _bute = (RegularExpressionAttribute)_attribute;_attri.Name = "RegularExpression";_attri.Args1 = _bute.Pattern.ToString();}if (!String.IsNullOrEmpty(_attri.Name)){_attributes.Add(_attri);}}if (_attributes.Count > 0){_cpro.Attributes = _attributes;}if (!String.IsNullOrEmpty(_cpro.Summary)){_cpro.Title = _cpro.Summary.Split(' ')[0];if (_cpro.Summary.Contains(" ")){_cpro.Desc = _cpro.Summary.Split(' ')[1];}}_pro_list.Add(_cpro);}_classModel.Properties = _pro_list;if (!String.IsNullOrEmpty(_classModel.Summary)){_classModel.Title = _classModel.Summary.Split(' ')[0];if (_classModel.Summary.Contains(" ")){_classModel.Desc = _classModel.Summary.Split(' ')[1];}}var _abc = "";_abc.ToLowerInvariant();return _classModel;}/// <summary>/// 首字母转化成小写/// </summary>/// <param name="input"></param>/// <returns></returns>public static string FirstLetterToLower(this string input){if (string.IsNullOrEmpty(input)){return input; // 如果字符串为空或null,直接返回}char firstChar = input[0];if (char.IsUpper(firstChar)){firstChar = char.ToLower(firstChar, CultureInfo.InvariantCulture);}return firstChar + input.Substring(1);}/// <summary>/// 模型的信息/// </summary>public class VoloModelInfo{/// <summary>/// 中文名称 注释的空格前部分/// </summary>public string Title { get; set; }/// <summary>/// 描述 注释的空格后部分/// </summary>public string Desc { get; set; }/// <summary>/// 文档注释/// </summary>public string Summary { get; set; }/// <summary>/// 主键类型/// </summary>public string KeyType { get; set; } = "int";/// <summary>/// 字段信息/// </summary>public List<VoloModelProperty> Properties { get; set; }}/// <summary>/// 模型的字段信息/// </summary>public class VoloModelProperty{/// <summary>/// 字段名称 CreateDate/// </summary>public string Name { get; set; }/// <summary>/// 字段类型 System.DateTime/// </summary>public string Type { get; set; }/// <summary>/// 字段默认值 0001/1/1 0:00:00/// </summary>public string Default { get; set; }/// <summary>/// 字段中文 创建日期/// </summary>public string Title { get; set; }/// <summary>/// 注释的后部分 空格之后的,一般用于描述/// </summary>public string Desc { get; set; }/// <summary>/// XML文档注释内容/// </summary>public string Summary { get; set; }/// <summary>/// 字段属性规则/// </summary>public List<VoloModelAttribute> Attributes { get; set; }}/// <summary>/// 字段的属性规则/// </summary>public class VoloModelAttribute{/// <summary>/// 验证名称 MaxLength/// </summary>public string Name { get; set; }/// <summary>/// 过滤器值1 128/// </summary>public string Args1 { get; set; }/// <summary>/// 过滤器值2/// </summary>public string Args2 { get; set; }/// <summary>/// 过滤器值3/// </summary>public string Args3 { get; set; }/// <summary>/// 过滤器验证信息/// </summary>public string ErrorMessage { get; set; }}}
}
上面就是读取某一个Dto模型的代码,怎么引用呢?
可以在对应的AppService中写一个接口,比如
/// <summary>/// 读取AddDto的数据模型/// </summary>/// <returns></returns>[HttpGet]public PasteBuilderHelper.VoloModelInfo ReadAddModel(){var _model = PasteBuilderHelper.ReadModelProperty<FrontTableAddDto>(new FrontTableAddDto());return _model;}/// <summary>/// 读取UpdateDto的数据模型/// </summary>/// <returns></returns>[HttpGet]public PasteBuilderHelper.VoloModelInfo ReadUpdateModel(){var _model = PasteBuilderHelper.ReadModelProperty<FrontTableUpdateDto>(new FrontTableUpdateDto());return _model;}
上面的代码可以写一个通用的读取的,不过那样和安全原则冲突,比如读取了你的配置文件模型,那就噶了,所以个人认为还是每个都写一个接口,反正都是PasteBuilder代码生成器的干活!
通过以上的接口,那么只要知道要给某一个数据模型做表单,就可以知道这个数据表中的各个字段的信息,包括表的主键类型等!
如果你要给UserInfo做表单,也就是新增,或者编辑,由PasteBuilder和PasteTemplate结合后可知道UserInfo的AppService对应的接口!
下一期将实现,如何在小程序上实现自动表单!就是通过引入几个组件,然后配置modelData,就可以实现表单的自动验证,这样就避免以前那种每个表单去改代码的痛苦了!
上面为一个案例表单的内容,输入内容后,就可以点击确定,读取对应的数据模型!
然后要实现这个表单的调用,代码只有下方一点:
<view>
<view>创建项目</view><view class="add-from" ><paste-form id="paste-form"></paste-form><view ></view><button bindtap="submitForm" style="margin-top:100rpx;" class="form-submit" type="primary">确定</button></view>
</view>
对应的js内容
// pages/form/work.js
Page({/*** 页面的初始数据*/data: {formData:[{name:"name",title:"姓名",type:1,placeholder:"请输入姓名",value:"",required:true,maxlength:5,pattern:"[a-z]{3,5}"},{name:"age",title:"年龄",type:2,placeholder:"请输入年龄",value:7,routes:[{name:"min",value:5,error:"最小值不能小于5,请重新输入"}]},{name:"size",title:"规格",type:3,placeholder:"选择大的那个",desc:"选择小的那个",value:"",required:true,array:["小号","中号","大号"]},{name:"isEnable",title:"状态",type:4,placeholder:"请选择",value:true},{name:"mark",title:"备注",type:5,placeholder:"这里输入备注内容",value:"",currentlength:0,maxlength:200},{name:"like",title:"爱好",type:6,placeholder:"",value:"",currentlength:0,items:[{name:"1",checked:"true",value:"足球运动"},{name:"2",value:"篮球运动"}]},{name:"workClassId",title:"作业分类",type:7,placeholder:"选择所属分类",value:"",display:"",path:"/pages/select/work_class/index?select=workClassId",},{name:"startDate",title:"开始日期",type:8,placeholder:"点击选择日期",value:""},{name:"startTime",title:"开始时间",type:9,placeholder:"点击选择时间",value:""},{name:"imgsimgs",title:"附件",type:10,num:3,placeholder:"",value:"",images:[]},{name:"examineDate",title:"日期时间",type:11,placeholder:"点击选择",value:"2024-09-12 00:00:00"},{name:"fromArea",title:"所属地区",dataType:'region',placeholder:"点击选择",value:"",level:"sub-district"},{name:"loginPass",title:"设置密码",dataType:'password',placeholder:"3~16位数,大小写和数字组成的密码",value:"",maxlength:16},{name:"datePlan",title:"班次选择",type:14,placeholder:"点击选择",value:"2024-08-12 下午"},{name:"bigRate",title:"分佣比例",type:15,placeholder:"请输入",value:""},{name:"body",title:"正文内容",type:16,placeholder:"请基于实际情况填写需求,支持图片等模式!",value:""}]},init() {let dom = this.selectComponent("#paste-form");dom.FuncInitForm(this.data.formData);},/*** 生命周期函数--监听页面加载*/onLoad(options) {this.init();},SetFormItemValue(_name,_value,_display){console.log("选择回传",_name,_value,_display);let dom = this.selectComponent("#paste-form");if(dom){dom.FuncSetValue(_name,_value,_display);}},submitForm() {// console.log(this.data.formData);let dom = this.selectComponent("#paste-form");var _info =dom.FuncParseForm();if(_info){console.log(_info);}},/*** 生命周期函数--监听页面初次渲染完成*/onReady() {},/*** 生命周期函数--监听页面显示*/onShow() {},/*** 生命周期函数--监听页面隐藏*/onHide() {},/*** 生命周期函数--监听页面卸载*/onUnload() {},/*** 页面相关事件处理函数--监听用户下拉动作*/onPullDownRefresh() {},/*** 页面上拉触底事件的处理函数*/onReachBottom() {},/*** 用户点击右上角分享*/onShareAppMessage() {}
})
注意看上面的JS,其实只有2个函数和一个配置的data内容
而json中只是引入了组件
{"usingComponents": {"paste-form":"/components/paste-form/paste-form"}
}
样式文件,更没有!
下期将介绍这个paste-form的组件内容!