如何优雅的实现CRUD,包含微信小程序,API,HTML的表单(一)

前言

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值