form表单页面在业务复杂之后,存在很多的数据操作函数,并且大部分的数据操作也是基于form表格数据的。
    因此设想使用class去封装这些数据以及所有的数据操作函数,也是貌似可行的。

    以下是根据现有业务的一些设想
    如下销售订单编辑的表单页面为例
    image.png

    1. //一般我们习惯在data中定义form数据对象,由于vue的观察机制,所以我们需要在form对象中定义出form
    2. //中含有的变量,否在vue会检查不到该属性值的变化,可能会导致一些渲染问题
    3. data(){
    4. return {
    5. form:{
    6. name:'',
    7. id:'',
    8. ...
    9. }
    10. }
    11. }

    由于表单字段比较多,一次在data中定义的字段属性也就比较多,会导致vue中的代码冗长,但是并没有实际的逻辑代码。
    所以我们或许会将这些参数的定义单独拿出来写,例如

    1. //data.js
    2. const form ={
    3. name:'',
    4. id:'',
    5. ...
    6. }
    7. export default data

    然后在vue中引入,并赋值给data中的对应值,然后将form的一些数据操作,交互逻辑放在vue的methods中,
    这些都是顺其自然的做法,但是由于vue的单文件形势,form的大量html模版语言已经比较冗长,再加上form的相关操作逻辑,代码动则上千,后期的功能复杂之后,methods也越来越多。导致后期维护困难,代码也比较不利于阅读。所以是否可以做一些代码封装,把相互关联的操作逻辑放在一起,即便与理解,也便于使用。

    所以就想到了使用class来封装form数据。看上去form的使用也比较贴合class的特性。form的参数都可以放在class的实例属性中,而相关的数据操作方法则可以放在class的实例方法上,由于form的数据操作大部分也是基于form,因此也正好可以this直接指向form本身,和vue的写法并没有太大的出入。封装完之后,vue中只需要调用class中暴露的实例方法来完成数据的操作, 让vue文件代码更加纯净,易读,维护也会方便一些

    先大致看一下以上业务的form表单大致需要完成哪些数据操作:

    • 参数的初始化,接口获取到数据之后,需要赋值给form
    • 关联数据的变化,例如获取到省市区县三者的id,需要拼接成数组形势的地区数据
    • form的重置

    目前看大致也就以上三点。而逻辑冗长的主要就是第二点的数据联动。

    以下是设想的各个需求的实现:

    1. //首先我们先创form的class
    2. class Form {
    3. constructor(obj={}){
    4. this.name='';
    5. this.id='';
    6. ...
    7. this.reloadForm(obj);
    8. }
    9. }

    使用assign初始化form参数,如果只需要一个空数据的form,则只需要new Form() 而不需要传参。
    考虑到接口的返回数据,一般会比本地定义的数据要多。所以在获取到接口数据之后,直接重新 new Form(data),
    以此来初始化所有的参数,确保vue可以检测到所有的属性值的变化来渲染页面。
    因此class不应提供reLoad等方法来重置数据,会存在一点的风险,直接重新创建实例。

    而reSet之类的重置数据方法是可以提供的,在reSet之前,所有的属性已经被实例注册,直接置为空值即可。

    1. //首先我们先创form的class
    2. class Form {
    3. constructor(obj={}){
    4. this.name='';
    5. this.id='';
    6. ...
    7. Object.assign(this,obj);
    8. }
    9. reSet(){
    10. Object.keys(this).forEach(key=>{
    11. this[key]=null
    12. })
    13. }
    14. }

    接下来处理数据的联动
    由于element组件的设计,联级组件的值是一个数组,层级制依次往后,但是接口存储和获取的增是分开的id值,
    比如省市区的值,接口是返回的loadProvinceId,loadCityId,loadDistrictId三个值,传入组件时我们必须将他们拼接,
    unloadArea,loadArea只有在重载数据和用户通过组件修改时才会变化,所以拼接只需要考虑在重载数据时就可以,所以我们可以直接在构造器中数据初始化完调用拼接

    1. class Form {
    2. constructor(obj={}){
    3. this.name='';
    4. this.id='';
    5. ...
    6. Object.assign(this,obj);
    7. this.reunloadArea()
    8. this.reloadArea()
    9. }
    10. reSet(){
    11. Object.keys(this).forEach(key=>{
    12. this[key]=null
    13. })
    14. }
    15. reunloadArea(){
    16. this.unloadArea=this.unloadProvinceId&&this.unloadCityId
    17. &&this.unloadDistrictId?
    18. [this.unloadProvinceId,this.unloadCityId,this.unloadDistrictId]:[]
    19. }
    20. reloadArea(){
    21. this.loadArea=this.rovinceId&&this.loadCityId&&this.loadDistrictId?
    22. [this.rovinceId,this.loadCityId,this.loadDistrictId]:[]
    23. }
    24. }

    其他参数的联动,比如选取供应商之后,赋值对应的name、code等值,一般都是根据一个支撑数据来做匹配,
    设计一个函数,接受三个值forCreate对应支撑数据数组,attrs需要修改的数据及对应键数组,匹配规则

    参数名 格式 备注
    forCreate [{
    id:1,name:’a’,code:’b’
    }]
    attrs [{
    prop:’loadSiteName’,attr:’extra1’
    }]
    prop对应form中的对应属性名,
    attr对应支撑数据中的属性名
    rule item=>item.id==value 匹配的函数
    1. setAttrs(forCreate=[],attrs=[],rulr=()=>false){
    2. const data = forCreate.find(rule) || {};
    3. let setValueProps = [];
    4. let setNullProps = [];
    5. const tempForm = {};
    6. attrs.forEach(({ prop, label }) => {
    7. if (data[label]) {
    8. tempForm[prop] = data[label];
    9. setValueProps.push(prop);
    10. } else {
    11. tempForm[prop] = null;
    12. setNullProps.push(prop);
    13. }
    14. });
    15. this.reloadForm(tempForm);
    16. return { setValueProps, setNullProps }; //抛出修改过的属性,以便做更多的扩展处理
    17. }
    18. //重置表单数据,内部使用,外部使用如果构造器中无此属性会导致vue监听无效
    19. reloadForm(obj) {
    20. Object.assign(this, obj);
    21. if (obj.hasOwnProperty('unloadDistrictId')) {
    22. this.reSetUnloadArea();
    23. }
    24. if (obj.hasOwnProperty('loadDistrictId')) {
    25. this.reSetLoadArea();
    26. }
    27. }

    扩展处理比如
    image.png

    在组件的chang事件中调用该方法

    1. <el-select v-model="value" placeholder="请选择" @change="(value)=>{
    2. this.reSet(value,this.forCreate.loads)
    3. }">
    4. <el-option
    5. v-for="item in forCreate.loads"
    6. :key="item.value"
    7. :label="item.label"
    8. :value="item.value"
    9. :disabled="item.disabled">
    10. </el-option>
    11. </el-select>
    12. //
    13. methods:{
    14. reset(value,forCreate){
    15. this.form.setAttrs(forCreate,[{prop:'name',attr:'extra1'},
    16. {prop:'code',attr:'extra2'}],
    17. item=>item.id==value)
    18. }
    19. }

    最后提交form,需要获取form中的所有值,同样的,loadArea之类拼接的数值,也是需要拆分提交的

    1. loadAreaIds(){
    2. const [loadProvinceId,loadCityId,loadDistrictId] = this.loadArea
    3. return {loadProvinceId,loadCityId,loadDistrictId}
    4. }
    5. unloadAreaIds(){
    6. const [unloadProvinceId,unloadCityId,unloadDistrictId] = this.unloadArea
    7. return {unloadProvinceId,unloadCityId,unloadDistrictId}
    8. }
    9. getFormData(){
    10. return Object.assign({},this,this.loadAreaIds(),this.unloadAreaIds())
    11. }
    12. //vue中使用
    13. this.form.getFormData()

    数据过滤也可以加入其中

    1. getFormData(acceptableHeaderColumnKeys=[]){
    2. const form = Object.assign({},this,this.loadAreaIds(),this.unloadAreaIds())
    3. return ObjectRemoveNull(filterAttrsData(form,acceptableHeaderColumnKeys))
    4. }

    以上的设想是为了简化vue主函数中的代码量,让form的操作单独维护,相关的数据操作封装在一起,也可以防止一些类似的数据操作,多处使用时遗漏相关的数据联动等

    最后完成form定义代码:

    1. class Form {
    2. constructor(obj = {}) {
    3. this.orderNo = '';
    4. this.loadArea = [];
    5. this.loadSiteCode = '';
    6. this.unloadSiteCode = '';
    7. this.loadProvinceId = '';
    8. this.loadCityId = '';
    9. this.loadDistrictId = '';
    10. this.unloadProvinceId = '';
    11. this.unloadCityId = '';
    12. this.unloadDistrictId = '';
    13. this.unloadArea = [];
    14. this.planNo = '';
    15. this.bizType = '';
    16. this.salesOrgId = '';
    17. this.itemId = '';
    18. this.deliveryMode = '';
    19. this.requestedQty = '';
    20. this.uom = '';
    21. this.currency = '';
    22. this.salesOfficeId = '';
    23. this.customerId = '';
    24. this.customerName = '';
    25. this.unloadSiteId = '';
    26. this.unloadSiteName = '';
    27. this.unloadAddress = '';
    28. this.salesUnitPrice = '';
    29. this.salesAmount = '';
    30. this.requestedUnloadDate = '';
    31. this.customerOrderNo = '';
    32. this.supplierId = '';
    33. this.loadSiteName = '';
    34. this.loadAddress = '';
    35. this.purchaseUnitPrice = '';
    36. this.purchaseAmount = '';
    37. this.requestedLoadDate = '';
    38. this.lotNo = '';
    39. this.dueTo = '';
    40. this.customerContact = '';
    41. this.customerNumber = '';
    42. this.unloadRemarks = '';
    43. this.itemCode = '';
    44. this.itemDescription = '';
    45. this.specialRequirement = '';
    46. this.orderRemarks = '';
    47. this.settlement = '';
    48. this.settlementName = '';
    49. this.reloadForm(obj);
    50. }
    51. //拼接装货地区
    52. reSetLoadArea() {
    53. this.loadArea =
    54. this.loadProvinceId && this.loadCityId && this.loadDistrictId
    55. ? [this.loadProvinceId, this.loadCityId, this.loadDistrictId]
    56. : [];
    57. }
    58. //拼接卸货地区
    59. reSetUnloadArea() {
    60. this.unloadArea =
    61. this.unloadProvinceId && this.unloadCityId && this.unloadDistrictId
    62. ? [this.unloadProvinceId, this.unloadCityId, this.unloadDistrictId]
    63. : [];
    64. }
    65. //处理地区数据
    66. loadAreaIds() {
    67. const [loadProvinceId, loadCityId, loadDistrictId] = this.loadArea;
    68. return { loadProvinceId, loadCityId, loadDistrictId };
    69. }
    70. //处理地区数据
    71. unloadAreaIds() {
    72. const [unloadProvinceId, unloadCityId, unloadDistrictId] = this.unloadArea;
    73. return { unloadProvinceId, unloadCityId, unloadDistrictId };
    74. }
    75. //表单置空
    76. resetForm() {
    77. Object.keys(this).forEach(key => {
    78. this[key] = null;
    79. });
    80. }
    81. //设置关联字段
    82. setAttrs(forCreate = [], attrs = [], rule = () => false) {
    83. const data = forCreate.find(rule) || {};
    84. let setValueProps = [];
    85. let setNullProps = [];
    86. const tempForm = {};
    87. attrs.forEach(({ prop, label }) => {
    88. if (data[label]) {
    89. tempForm[prop] = data[label];
    90. setValueProps.push(prop);
    91. } else {
    92. tempForm[prop] = null;
    93. setNullProps.push(prop);
    94. }
    95. });
    96. this.reloadForm(tempForm);
    97. return { setValueProps, setNullProps }; //抛出修改过的属性,以便做更多的扩展处理
    98. }
    99. //重置表单数据,内部使用,外部使用如果构造器中无此属性会导致vue监听无效
    100. reloadForm(obj) {
    101. Object.assign(this, obj);
    102. if (obj.hasOwnProperty('unloadDistrictId')) {
    103. this.reSetUnloadArea();
    104. }
    105. if (obj.hasOwnProperty('loadDistrictId')) {
    106. this.reSetLoadArea();
    107. }
    108. }
    109. //获取表单数据
    110. getFormData(acceptableHeaderColumnKeys = []) {
    111. const form = Object.assign(
    112. {},
    113. this,
    114. this.loadAreaIds(),
    115. this.unloadAreaIds()
    116. );
    117. return ObjectRemoveNull(filterAttrsData(form, acceptableHeaderColumnKeys));
    118. }
    119. }

    目前在销售订单编辑页面有实践,需求暂时满足
    image.png