vue2 / el-table的简单封装

652 阅读2分钟

前言

element里面的table组件在我所在的公司项目中是最常用到的组件,这个组件十分的灵活,它自带了自定义单元格,自定义表头,实现列固定等许多方便的api。

但是,如果你的table组件只是单纯的展示单元格内容,不同的可能只是列的名称和数据,那么你会发现每次都要写一堆的template是十分的痛苦。

因此,我对el-table进行了二次封装,在不改动其可自定义编辑单元格的基础上,对其进行通过写配置的方式进行渲染,减少工作量。

组件的设计

我只需要传入两个参数,一个是columns,一个是data

  props: {
    //  列配置
    columns: {
      type: Array,
      require: true,
    },
    //  数据
    data: {
      type: Array,
      require: true,
    },
  },

使用方法(参考)

    <ETable :columns="columns" :data="tableData">
      <template #age="{ row }">
        <el-input v-model="row.age" />
      </template>
    </ETable>
  data() {
    return {
      columns: [
        {
          type: "selection",
          fixed: "left",
        },
        {
          prop: "name",
          label: "名字",
          width: "300",
        },
        {
          prop: "age",
          label: "年龄",
          width: "300",
        },
        {
          prop: "sexual",
          label: "性别",
          fixed: "right",
        },
      ],

      tableData: [
          {
            name: "test-name",
            age: "test-age",
            sexual: "male"
          }
      ],
    };
  },

组件的实现

我们翻看官方文档,发现el-table-column提供了 scopedSlots 这个属性。那么,思路就明确了,如果组件中对应的列没有template,那么我们自己做一个默认的 template 展示数据即可。

export default {
  name: "ETable",
  props: {
    columns: {
      type: Array,
      require: true,
    },
    data: {
      type: Array,
      require: true,
    },
  },
  data() {
    return {};
  },

  methods: {
    renderScopedSlot(propName, scopedSlots) {
      //  说明是column自带的选择框或者序号等
      if (!propName) {
        return;
      }
      
      //  默认的模板
      const tmp = {
        default: ({ row }) => <span>{row[propName]}</span>,
      };
      
      //  有提供就用提供的模板
      if (scopedSlots[propName]) {
        tmp.default = scopedSlots[propName];
      }

      return tmp;
    },
  },

  render() {
    const {
      $attrs,
      $listeners,
      $scopedSlots,
      data,
      columns,
      renderScopedSlot,
    } = this;
    
    //  兼容el-table的api和事件
    const tableProps = {
      on: $listeners,
      attrs: $attrs,
    };

    return (
      <div class="e-table">
        <el-table data={data} {...tableProps}>
          {columns.map(({ label, prop, ...otherProps }) => (
            <el-table-column
              key={label}
              label={label}
              scopedSlots={renderScopedSlot(prop, $scopedSlots)}
              {...{
                props: { ...otherProps },
              }}
            ></el-table-column>
          ))}
        </el-table>
      </div>
    );
  },
};

测试

<template>
  <div>
    <ETable :columns="columns" :data="tableData" @selection-change="handleSelectionChange">
      <template #name="{ row }"> {{ "测试slot" + row.name }} </template>
      <template #age="{ row }">
        <el-input v-model="row.age" />
      </template>
    </ETable>
  </div>
</template>
import ETable from "./components/index.vue";
export default {
  components: { ETable },
  props: {},
  data() {
    return {
      columns: [
        {
          type: "selection",
          fixed: "left",
        },
        {
          prop: "name",
          label: "名字",
          width: "300",
        },
        {
          prop: "age",
          label: "年龄",
          width: "300",
        },
        {
          prop: "area",
          label: "地区",
          width: "300",
        },
        {
          prop: "test1",
          label: "测试1",
          width: "300",
        },
        {
          prop: "test2",
          label: "测试2",
          width: "300",
        },
        {
          prop: "sexual",
          label: "性别",
          fixed: "right",
        },
      ],

      tableData: [],
      
      rowSelection: [],
    };
  },
  mounted() {
    this.tableData = this.generateData(this.columns);
  },
  methods: {
    generateData(columns, num = 100) {
      let arr = [];

      for (let i = 0; i < num; i++) {
        arr.push(
          Object.assign(
            {},
            columns.reduce((prev, curr) => {
              return {
                ...prev,
                [curr.prop]: ((i + 1) * Math.random()).toFixed(3),
              };
            }, {})
          )
        );
      }

      return arr;
    },
    handleSelectionChange(selection) {
      this.rowSelection = selection;
    },
  },
};

效果

test.gif

可以看到,无论是element自带的选择框,还是固定列都能使用。同时,我自定义的nameage列可以自定义,同时其他列是正常的渲染。