前言
在开发实际项目中,其实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的组件内容!