vue 仿el-table原理实现表格

element-ui是国内最流行的的vue开源框架,el-table组件在element-ui整个框架中是最复杂、最重要的部分。其中涉及到的知识有JSX,render渲染函数,组件间的状态管理等等。出于好奇和挑战,在网上受教于el的源代码以及网上相关内容资料。完成了一个简单基础的table组件 就叫 sd-table吧。

支持功能: 自定义列、支持自定义插槽、支持自定义排序、支持全选、多选、分页回调、宽高样式等。

el-table 源码传送门:element: Element 是一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库,提供了配套设计资源,帮助你的网站快速成型。由饿了么公司前端团队开源。 - Gitee.com

1.效果及使用方法

1.1 使用方法:

使用方法如下,看看是不是特别熟悉?与 el-table是否类似呢?


    
    
    
        
    
    
    
    
  

        1.2效果图vue 仿el-table原理实现表格_第1张图片

2.代码

2.1  sd-table-body.js

        sd-table-body 主要是渲染整体表格,使用render 函数 循环二维数组 遍历tr行 遍历 td列。

export default {
  name:'sd-table-body',
  computed:{
    table(){
      return this.$parent
    }
  },
  props:{
    store: {
      require: true
    }
  },
  methods:{
    getBodyCellStyle(column){
      return {
        width: column.width + 'px',
        'text-align': column.align
      }
    },
  },
  render(h) {
    let tableData = this.table.tableData ;
    let columns = this.store.getColumns() ;
    let tableWidth = this.store.getTableWidth() ;
    return (
      
        {tableData.map((row, index) =>
          
            {columns.map(column => )}
          )
        }
      
{column.renderCell(row, index)}
) } }

2.2  sd-table-column.js

        sd-table-column.js 此组件主要是渲染 列相关内容,判断type 是selection或 index 或者是自定义插槽。若type=“selection” 为多选框,type=“index” 为 行序号 自定义插槽则直接显示自定义插槽内容,插槽内容可以为组件或html。

代码如下:

let columnIdSeed = 0 ;
export default  {
  name: 'sd-table-column',
  computed:{
    tableColumnId(){
      return 'columnId-' + (columnIdSeed++)
    },
    table(){
      return this.$parent
    },
  },
    props:{
      label: String,
      prop: String,
      width: String,
      align: String,
      type: String,
      sortable:{
        type:Boolean,
        default:false,
      }
    },
  data(){
    return{
      column:{},
      value_2: false,
    }
  },
    methods:{
      renderCell(data,index){
        let curIndex = index + 1 ;
        if(this.type ==='index') {
          return curIndex ;
        }
        if(this.type ==='selection') {
          this.column.renderCell = (data,index) => (
            
this.selectionChange($event, data) } id={index}/>
); return ; } if(this.prop) return data[this.prop] return this.$scopedSlots.default({ $index: curIndex, row: data }) }, selectionChange(event,row){ let isExist = false ; if(row.$checked){ //row.$checked = !row.$checked this.$set(row,'$checked',!row.$checked); }else{ this.$set(row,'$checked',true); //row.$checked = true ; } if(row.$checked){ this.table.selectRowData.forEach((item)=>{ if(item == row){ isExist = true ; } }) if(!isExist){ this.table.selectRowData.push(row) ; } }else{ for(let i = 0 ; i < this.table.selectRowData.length ; i ++){ if(this.table.selectRowData[i] == row){ this.table.selectRowData.splice(i,1); } } } this.table.$emit("selection-change",this.table.selectRowData) ; }, test(event,data){ console.log(event.target.id); } }, watch:{ value_2(newVal){ console.log(newVal) } }, created(){ this.column = { tableId: this.table.tableId, columnId: this.tableColumnId, label: this.label, prop: this.prop, originWidth: this.width, width: 0 , slots: this.$slots.default, align: this.align, renderCell: this.renderCell, type: this.type, sortable:this.sortable, } this.table.store.insertColumn(this.column) }, render(h){ return h('div', this.$slots.default) ; } }

2.3  sd-table-head.js

此组件是表头内容,组件拿到columns 这个表头数组,通过render函数内遍历可得到表头行,这里涉及到一个chekbox排序。 不涉及复杂表头,复杂表头以后研究。

export default {
  name: 'sd-table-head',
  data(){
    return{
      columns:[],
      // 实现排序功能
      "asc-n": (a, b) => a - b,
      "desc-n": (a, b) => b - a,
      "asc-s": (a, b) => a.localeCompare(b),
      "desc-s": (a, b) => b.localeCompare(a),
    }
  },
  computed:{
    table(){
      return this.$parent
    }
  },
  props:{
    store:{
      require: true
    }
  },
  methods:{
    getHeaderCellStyle(column){
      return {
        width: column.width + 'px'
      }
    },
    handleSortClick(event, column, givenOrder) {
      event.stopPropagation();
      if (!column.sortable) return;
      this.$set(this.table,'order',givenOrder);
      console.log(this.table.order)
      this.sort(givenOrder,column) ;
      this.$emit('sort-change',givenOrder);
  },
    sort(type,column) {
      // 是否排序
      if (column.sortable) {
        // 对父父亲元素进行排序
        this.$parent.tableData.sort((a, b) => {
          // 调用排序规则,传入排序方式asc、desc,然后判断数据类型进行拼接后调用方法实现排序
          return this[type + this.getSortType(a[column.prop])](
            a[column.prop],
            b[column.prop]
          );
        });
      }
    },
    selectAll(){
      this.table.isSelectedAll = !this.table.isSelectedAll;
      if(this.table.isSelectedAll ){
        this.table.toggleRowSelection(this.table.tableData) ;
      }else{
        this.table.clearSelection();
      }
    },
    // 处理排序类型
    getSortType(val) {
      // 返回标识串进行拼接
      return typeof val === "string" ? "-s" : "-n";
    },
  },
  created(){
    this.columns = this.store.getColumns() ;
  },
  render(h) {
    let columns = this.store.getColumns() ;
    let tableWidth = this.store.getTableWidth() ;
    return (
      
            {

              {columns.map(column=>
                  
              )}
            
            }

      
{ (column.type =='selection')? ( this.selectAll() }/>): (column.label) } { column.sortable ? ( this.handleSortClick($event, column, 'asc') }> this.handleSortClick($event, column, 'desc') }> ) : '' }
); }, }

2.4  sd-table-store.js

store 主要是保存列数据以及表的基本数据信息,方便以上各个组件初始化使用

export default class SdTableStore {
  constructor(tableId) {
    this.storeid = 'store-' + tableId ;
    this.columns = [] ;
    this.columnLabelMap = {} ;
    this.realTableWidth = 0 ;

  }
  insertColumn (column){
    this.columns.push(column) ;
    this.columnLabelMap[column.columnId] = column.label
  }
  getColumns(){
    return this.columns ;
  }
  updateTableWidth (width) {
    this.realTableWidth = width ;
  }
  getTableWidth () {
    return this.realTableWidth ;
  }
}

2.5 util.js

此工具方法为了计算列宽。


function  calcColumnWidth (columns, table) {
  let bodyWidth = table.$el.clientWidth - 24
  let tableWidth = 0
  let flexColumns = []
  flexColumns = columns.filter(column => {
    if (typeof column.originWidth !== 'string') return column
  })
  for (let column of columns) {
    column.width = column.originWidth || 80
    tableWidth += parseInt(column.width)
  }
  // 宽度有富余
  if (tableWidth < bodyWidth) {
    // 富余宽度
    let flexWidth = bodyWidth - tableWidth
    let flexSpaceWidth = parseInt(flexWidth / flexColumns.length)
    flexColumns[0].width += flexWidth - flexSpaceWidth * (flexColumns.length - 1)
    for (let i = 1; i < flexColumns.length; i++) {
      flexColumns[i].width += flexSpaceWidth
    }
    table.store.updateTableWidth(bodyWidth)
  } else {
    table.store.updateTableWidth(tableWidth)
  }
  return columns ;
}
export {calcColumnWidth}

2.6 sd-table.vue

此组件是以上组件的父组件,负责组织和调用以上组件。

代码如下:






2.6 test-table.vue

test-table.vue 用于调用测试表格使用。mbutton 为我自定义的组件,请自行去掉或替换。

我们可以看到基本与el-table 的使用用法一致。当然目前功能不完善,功能性不如element-ui。但是自定义插槽、多选排序以及分页功能是我们经常用到的,足够用了。






感谢阅读!

你可能感兴趣的:(jsx,el-table,自定义table组件)