vue-element 封装一个table单选&多选组件

493 阅读3分钟

「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战

vue-element 封装一个table选择组件

一些开发过程中的坑

这篇文章是20年年初写的,当时封装了个table选择组件,支持多选和单选,过程中踩了很多坑,也学到了很多,(以下都是基于Vue.js2.x):

  1. props 传回来的数据是不允许修改的,只能监听。
  2. 修改this中对象或数组的值时,最好先复制一份temp出来,对temp进行数据操作,再把整个temp赋到data中,原因是数组其实是一个引用值(改引用值指向真实的数据),当修改了数组中的数据,引用值其实是不变的,所以无法监听到数据有所更改而渲染页面。
  3. 关于elementdialog有个问题,我用dialog嵌套table,当table列数太多,width太大的时候,点击行时整个dialog都会跟着抖动了下,查了方法,改了很久,最后也……还是会。很心累,但是,我换成了element抽屉组件,就不会有这个问题。
  4. form表单的自动校验项要在data中初始化,不要在computed中定义,会有问题。
  5. 关于计算属性的问题,computed属性里默认只有getter,没有setter,即computed的数据是只读属性的。需要自己手动定义一下set方法。
    • get方法 是取,即给变量赋值
    • set方法 当变量值发生改变后会出发set方法。
  6. 记一个困扰了我1个多小时的问题~有个页面,调用了多个组件,其中有2个组件的name是一样的,导致我整个页面白屏,一直在重复请求一个接口。以后一定要注意每个组件的name值都设成唯一的值,不要重复。
  7. 另外,还有一个问题,关于select框的回显,我从接口已经拿到selectkey值,select选择列表也已经有了,但是渲染出来的就始终是key,没有显示label名称,排除了:
    • 设置setTimeout,确保列表数据已经拿到再来给选择赋值
    • v-if,让数据全部拿到之后重新渲染
    • this.$nextTick(()=>{}),强制刷新 ~~ 用了以上3个方法,仍然不行,最后用typeof 发现,列表里的返回的key类型是number,接口获取到的选择值是stirng,~~~~晕死~~~~

可跨页选择的table组件

封装el-table组件,命名为list-table

  1. 组件中定义传参:table请求的列表接口api(function)、查询参数(object)、单选||多选(boolean)、已选择的key(array),后续的再根据需求增加
  2. 通过$emit定义选择框的方法,并将选择的数据传回给父组件(含有点击行选择,点击checkbox多选,点击radio单选,全选,手动选择)
  3. 需要实现跨页选择,即点击分页跳转之后勾选数据,选择的列表要往上叠加,不能丢失。需要在el-table中添加:row-key="getRowKeys,el-table-column中添加:reserve-selection="true",定义type="selection",如下:
  <el-table-column :reserve-selection="true" :row-key="item.id" fixed="left" type="selection" class="selection" :prop="item.id" width="50" align="center"/>

getRowKeys方法:

getRowKeys(row) { return row[this.id] },
  1. 根据传过来的已选择的唯一ID数组,循环列表勾选上
  // 判断是否为选中状态
    ifChooseData() {
      if(this.multi) {
        this.$refs['listTableObject'].clearSelection();
        var tempList = this.tempList || this.tempData;
        for(let j=0; j<tempList.length; j++) {
          this.$nextTick(() => {
            this.list.forEach(item => {
              if(item[this.id] == tempList[j]) {
                this.$refs['listTableObject'].toggleRowSelection(item,true)
              } else {
                // this.$refs['listTableObject'].toggleRowSelection(item,false)
              }
            })
          })
        }
      }
    }
  1. 通过slot插槽,在父组件中自定义table列数据;并且添加分页组件,具体代码如下:
   <template>
     <div class="zsl-table">
       <el-table
         v-loading="queryLoading"
         :data="list"
         v-bind="$attrs"
         :default-sort="sort"
         highlight-current-row
         :row-key="getRowKeys"
         stripe
         fit
         border
         use-virtual
         :span-method="objectSpanMethod"
         ref="listTableObject"
         @current-change="getCurrentChange"
         @row-click="getRowSelect"
         @select="getChangeSelect"
         @select-all="getAllSelect"
         @selection-change="handleSelectionChange">
         <slot/>
       </el-table>
       <div class="zsl-table-pagination">
         <pagination :page="page" @paging="pages" />
       </div>
     </div>
   </template>

methods方法:

   getRowKeys(row) {
     return row[this.id]
   },
   async getList() {
     try {
       this.queryLoading = true
       const params = { ...this.params, ...this.page }

       if (typeof this.api !== 'function') {
         this.queryLoading = false;
         throw new Error('api应该传入一个方法')
         return;
       } else {
         const res = await this.api(params);
         let data = res.data.data
           this.list = data.datas;
           this.page = {
             pageNum: data.pageNum,
             pageSize: data.pageSize,
             total: data.total,
             pages: data.pages
           }
           this.$emit('getTableData', data.datas)
       }
       this.ifChooseData();
     } catch (e) {
       // throw new Error('处理异常')
       throw e
     }
     this.queryLoading = false;
   },
   // 判断是否为选中状态
   ifChooseData(){
     if(this.multi) {
       this.$refs['listTableObject'].clearSelection();
       var tempList = this.tempList || this.tempData;
       for(let j=0; j<tempList.length; j++) {
         this.$nextTick(() => {
           this.list.forEach(item => {
             if(item[this.id] == tempList[j]) {
               this.$refs['listTableObject'].toggleRowSelection(item,true)
             } else {
               // this.$refs['listTableObject'].toggleRowSelection(item,false)
             }
           })
         })
       }
     }
   },
   pages(newPage) {
     this.page = newPage;
     this.getList()
   },
   _resetPage() {
     this.page = defaultPage(this.page)
   },
   reload() {
     this.$nextTick(() => {
       this._resetPage()
       this.getList()
     })
   },
   // 暴露获取选中的方法
   get_listTableObject() {
     console.debug(this.$refs.listTableObject)
   },
   getCurrentChange(val) {
     this.currentRow = val;
     this.$emit('getCurrentChange',val)
   },
   getRowSelect(row,column,event) {
     this.$refs['listTableObject'].toggleRowSelection(row)
     this.$emit('getRowSelect',row,column,event)
   },
   getChangeSelect(selectItem,changItem){
     this.$emit('getChangeSelect',selectItem,changItem)
   },
   handleSelectionChange(val) {
     // this.$refs['listTableObject'].toggleRowSelection(val)
     this.$emit('handleSelectionChange', val)
   },
   getAllSelect(val) {
     this.$emit('getAllSelect', val)
   }

封装多选组件:

  1. 调用list-table组件
  2. 布局方面,用flex布局,左边70%的table,右边30%的选择列表,将选择的列表存到selectedList对象中,选中的唯一key存在tempList数组中,tempList用于遍历渲染选择列表框。点击选定退出或取消的时候,把selectedList传回给父组件。
  3. methods方法里,将list-table选择返回来的对象,进行判断,如果不存在则push进数组,存在则用splice方法去除项,关键代码如下: methods方法:
  getTableData(data) {
    this.tableList = data;
  },
  reloadTable() {
    this.$refs.tableData.reload();
  },
  resetQuery() {
    this.param = this.query;
    this.reloadTable();
  },
  handleSelectionChange(row) {
    var temp = [];
    var selectedList = {};
    for(let i=0;i<row.length;i++) {
      let item = row[i]
      let index = item[this.id]
      temp.push(index);
      selectedList[index] = item;
    }
    this.tempList = deepClone(temp)
    this.selectedList = deepClone(selectedList)
  },
  getRowSelect(row, column, event) {
    var temp = this.tempList;
    let index = temp.findIndex(item => item == row[this.id]);
    let i_index = row[this.id]
    if(index>=0) {
      temp.splice(index, 1);
      delete this.selectedList[i_index]
    } else {
      temp.push(i_index);
      this.selectedList[i_index] = row;
    }
    this.tempList = deepClone(temp)
  },
  getChangeSelect(selectItem,changItem){
    var temp = this.tempList;
    let i_index = changItem[this.id]
    if(selectItem.indexOfObj(changItem,this.id) >= 0) {
      let index = temp.findIndex(k => k == i_index)
      if(index>=0) {

      }else {
        temp.push(i_index)
        this.selectedList[i_index] = changItem;
      }
    } else {
      let index = temp.findIndex(k => k == i_index)
      if(index >= 0) {
          temp.splice(index, 1)
          delete this.selectedList[i_index]
      } else {}
    }
    this.tempList = deepClone(temp)
  },
  getAllSelect(val) {
    var temp = this.tempList;
    if (val.length > 0) {
      for (let i = 0; i < val.length; i++) {
        let item = val[i];
        let i_index = item[this.id];
        if (temp.findIndex(i_item => i_item == i_index) < 0) {
          temp.push(i_index);
          this.selectedList[i_index] = item;
        } else {}
      }
    }else {
      for (let i = 0; i < this.tableList.length; i++) {
        let item = this.tableList[i]
        let i_index = item[this.id]
        let index = temp.findIndex(i_item => i_item == i_index);
        if (index >= 0) {
          temp.splice(index, 1);
          delete this.selectedList[i_index];
        } else {}
      }
    }
    this.tempList = deepClone(temp)
  },
  handleChoose() {
    this.$emit('handleChoose', this.selectedList)
  },
  cancelChoose() {
    this.tempList = []
    for(let k in this.selectedList) {
      delete this.selectedList[k]
    }
    this.selectedList = {};
    this.$emit('handleChoose', this.selectedList)
  }

封装单选组件:

  1. 就类似与多选组件,只是不需要定义那么多方法,只需要行点击方法,以及radio点击方法即可,并且不需要多加一个选择列的框框,在template中,把type='checkbox'el-table-column改成用template自定义成radio,代码跟上一个类似就不贴了。