iview可编辑表格组件封装

因为公司需要尝试新的UI框架,因此自己也是学习了iview这个新的框架,之前一直都是用的element-ui,正好公司项目用到可编辑表格这样的组件,但是网上也是搜不到相关的资料,所以自己在参考了iview-admin的一些方法之后,自己写了一个,当然这个组件还是有一些缺陷后面会说到,本篇就是记录一下自己学习新的ui框架的一个过程。

首先自己希望的可编辑的表格是这样的一种形式。
iview可编辑表格组件封装_第1张图片

就是这样的整行可以点击编辑的样式,看似好像很简单,但是其中有非常多的坑。首先按照自己的想法,就是讲表格中显示编辑和实际数据的地方抽象出来成为一个组件

//	inputEdit.vue



样式这里就忽略了,可以看到逻辑还是很简单的,就是根据不同的列选项,来展示相应的输入编辑框,可能很多同学看了觉得这个日期框可以复用,不用一个一个判断,当时也是这么想的,但是实现的时候发现,不同类型的日期框,显示的值也是不一样的,如果复用这个的话,父级组件上面的prop非常多,不利于这个组件的复用,而这个编辑组件其实内容还是比较少的。
搞定了这个以后,再来看整个表的结构,我们需要把这个组件放在一个表格的组件中,所以有了这样,

//	tableEdit.vue

博主这里是增加了一个添加行的功能,逻辑其实就是在表格数据上添加一行空数据,另外因为iview没有合计行的功能,所以博主自己也是写了一个类似element的合计行,看到这里其实我们刚刚的组件都还没有用上,别着急,其实iview的表格组件和element表格最大的区别就在于自定义方法的不同,iview是用了vue的render函数来构造的表格自定义的,而element则是将自定义的dom元素封装成了模板,拿来用就可以了,所以相比较而言,iview还是要复杂一些的。来看看我们是怎么使用我们刚刚的组件的。

//  动态渲染表格的每列的render函数
    suportEdit (item, index) {
      item.render = (h, params) => {
        return h(TablesEdit, {
          props: {
            params: params,
            value: this.insideTableData[params.index][params.column.key],
            tableData: this.insideTableData,
            options: this.options,
            editIndex: this.editIndex,
            columns: this.insideColumns,
            backupsRow: this.backups
          },
          on: {
            'on-editing': params => {
              this.tempSummary.splice(params.index, 1, params.row)
            },
            'on-delete': params => {
              this.insideTableData.splice(params.index, 1)
              this.tempSummary.splice(params.index, 1)
              if (params.row.id) {
                this.deleteData.push(params.row)
                this.$emit('delete-data',this.deleteData)
              } else {
                this.addData.splice(params.index, 1)
                this.$emit('add-data',this.addData)
              }
            },
            'on-cancel-edit': (params) => {
              this.editIndex = -1;
              if (this.isAddRow) {
                this.insideTableData.splice(params.index, 1)
                this.isAddRow = false;
              } else {
                this.insideTableData.splice(params.index, 1, this.backups)
                this.tempSummary = this.insideTableData.slice()
              }
              this.backups = {};
              this.$emit('get-save');
            },
            'on-start-edit': (params) => {
              if (this.editIndex !== -1) {
                this.$Message.warning({content: '请先保存数据'})
                return;
              }
              this.backups = Object.assign({}, params.row)
              this.editIndex = params.index
              this.$emit('not-save')
            },
            'on-save-edit': (params) => {
              // 验证输入
              let identity = this.identity.bind(this, params)
              if (identity()) {
                this.insideTableData[params.index] = params.row
                if (params.row.id) {
                  let flag = false
                  this.updateData.forEach(item => {
                    if (item.id === params.row.id) flag = true
                  })
                  //  设置标识,如果更新集合中的某项id等于当前编辑行的id,则表示数据并未更新,因此不添加到更新数组
                  if (!flag) {
                    this.updateData.unshift(params.row)
                  }
                  this.$emit('update-data',this.updateData)
                } 
                this.editIndex = -1;
                if (this.isAddRow) {
                  this.addData.push(params.row)
                  this.$emit('add-data',this.addData)
                  this.isAddRow = false
                } else if (!params.row.id) {
                  this.addData.splice(-1, 1, params.row)
                  this.$emit('add-data',this.addData)
                }
                this.backups = {}
                this.$emit('get-save')
              }
            }
          }
        })
      }
      return item
    },
    //  根据父组件传过来的列数据添加渲染函数
    handleColumns (columns) {
      this.insideColumns = columns.map((item, index) => {
        let res = item
        if (res.children) {
          let child = res.children
          child.forEach((item, index) => {
            item = this.suportEdit(item, index)
          })
        } else {
          res = this.suportEdit(res, index)
        }
        return res
      })
    },

整个逻辑其实很简单,只是非常复杂,稍不注意就可能产生了意向不到的bug,首先我们需要将父组件传过来的表格列的数组进行遍历,因为考虑到复杂表头的情况,所以我们需要深层去遍历,遍历以后,每一项执行suportEdit函数,这个函数其实就是iview当中封装的渲染函数,注意这个函数和原生的vue的渲染函数又有些不一样,上面的代码中可以看到,render函数有两个参数,一个就是我们熟悉的h渲染函数,另外一个则是一个对象params,这个params其实就是表格中的一些数据,有行数据,列数据,以及索引等等。其次我们通过this.$emit的方法,来向父组件传递一些自定义的事件,比如开始编辑,删除等按钮的事件,这个也是博主感觉不好的地方,因为数据流过于混乱,有传入子组件的数据,也有传入父组件的数据,并不是vue中倡导的单向数据流,后续维护可能会比较麻烦,因为追踪这些数据的流向可能就是一个比较大的工程,而博主这样做,也是因为公司项目中这样的表格非常多,如果每次都重复这样的组件的话,在对性能影响不多的情况,还是选择将这个组件抽象出来更好,所以如果你的项目中这样的表格并不多,还是iview每个列渲染一次就可以了。

tableEdit组件才是我们需要复用的组件,注意这里用了一些addData,updateData,deleteData这样的数组,其实就是编辑过程中,更改,增加以及删除的数据,最终是要传给服务端的,所以博主将这些数据抽离出来,最终当做参数传递给服务端,当然如果你只需要传入最终的表格数据作为参数的话,就不需要这样的一些数据。

这个组件最重要的地方,其实就是四个按钮,编辑,删除,保存,取消,这四个按钮逻辑看似简单,但是其实还是比较复杂的,首先点击编辑话,改变编辑状态editIndex,这样切换到编辑状态,同时输入数据修改的时候,要将编辑前的数据保存到临时数据backupRow中,因为每次编辑的值会传给v-model绑定的值,这样表格数据的值也会被改变了,本来没有问题,但是如果你点击了取消按钮,编辑的数据无效,但是还是改变了表格数据不就矛盾了吗,其次,当点击删除的时候,则将点击按钮的索引传递给表格数组,删除掉索引行的表格数据来实现删除,这个还是比较简单的,第三则是点击保存的时候,因为这里记录了update更新的数组,每次保存到服务端的时候,服务端会生成一个id,所以我们会判断一下update数组里面的每项的值,如果id属性相等,则表示这个数据就是之前修改的数据,就不需要将此行数据加入到update数组中,因为如果没有这样的判断的话,当你每次点击保存的时候,都会将当前行数据加入到update中,肯定不是我们想要的结果。最后取消按钮就是将编辑状态editIndex变成-1,还原成默认状态。

最后来看看合计功能的实现,合计功能就是参照了element的合计来实现的

computed: {
    summaryData () {
      let sum = {}
      let arr = []
      this.summaryColumns.forEach((column, index) => {
        if (index === 0) {
          sum.total = '合计';
          return;
        }
        if (column.key === 'handle' ) {
          sum.handle = '';
          return;
        }
        let values = this.tempSummary.map(item => Number(item[column.key]))
        //  只要values中有一个值为NaN就不进行累加计算
        if (!values.some(value => isNaN(value))) {
          sum[column.key] = values.reduce((prev, curr) => {
              const value = Number(curr);
              if (!isNaN(value)) {
                return prev + curr;
              } else {
                return prev;
              }
            }, 0);
          sum[column.key] = sum[column.key].toFixed(2);
        }
      })
      arr.push(sum)
      return arr;
    }
  },

逻辑就是我们在表格的slot里面又添加了一个table,这个table没有表头,只有数据项,我们根据主表格的数组来进行累加,用的就是reduce归并方法,不熟悉的同学可以去搜搜,然后规定第一列显示合计的字样,最后一列handle不进行操作,中间的数据项如果有一项不是数字的话,则不进行计算。当然这里考虑到普遍适用性,没有办法给每一列的合计数据添加好一个单位,所以暂时是这样,博主也是用了这样一个思路,在element上面也封装了一个近似的组件,因为这个iview的组件点击取消按钮的时候回有延迟的情况,博主研究了很久,知识有限也是没有找出原因,最后还是选择用element来封装,后续会将完整代码上传到github上面,如果对您有帮助的话,希望能不吝star。后续github地址

你可能感兴趣的:(vue,iview)