使用render函数二次封装el-table

4,864 阅读4分钟

前言

以数据驱动表格,减少繁琐的代码。

代码

props部分

    tableColumn: {
      type: Array,
      default: () => [
        {
          label: "序号",
          align: "center",
          width: 50,
          type: "index",
        },
        {
          prop: "name",
          label: "名字",
          align: "center",
        },
        {
          prop: "age",
          label: "年龄",
          align: "center",
        },
        {
          prop: "sex",
          label: "性别",
          align: "center",
        },
      ],
    },
    tableData: {
      type: Array,
      default: () => [
        {
          name: "拖鞋丢你脸上",
          age: 18,
          sex: "男",
        },
      ],
    },
    size: {
      type: String,
      default: "medium",
    },
    stripe: {
      type: Boolean,
      default: false,
    },
    border: {
      type: Boolean,
      default: false,
    },
    fit: {
      type: Boolean,
      default: true,
    },
    showHeader: {
      type: Boolean,
      default: true,
    },
    headerCellStyle: {
      type: Function,
      default: (/*{ row, column, rowIndex, columnIndex }*/) => {
        return { color: "#000" };
      },
    },
    cellStyle: {
      type: Function,
      default: (/** {row, column, rowIndex, columnIndex}*/) => {
        return { color: "#333" };
      },
    },
    pagination: {
      type: Object,
      default: () => ({
        current: 1,
        size: 10,
        total: 0,
      }),
    },
    pageSizes: {
      type: Array,
      default: () => [10, 20, 30, 50],
    },

render部分

  render(h) {
    const {
      tableData,
      size,
      stripe,
      border,
      fit,
      showHeader,
      headerCellStyle,
      cellStyle,
      sortChange,
      select,
      selectAll,
      selectionChange,
      tableColumn,
      // 分页
      pagination: { current, size: pageSize, total },
      pageSizes,
      sizeChange,
      currentChange,
    } = this;
    return h(
      "div",
      {
        class: {
          "render-table": true,
        },
      },
      [
        h(
          "el-table",
          {
            props: {
              data: tableData,
              size: size,
              stripe: stripe,
              border: border,
              fit: fit,
              "show-header": showHeader,
              "header-cell-style": headerCellStyle,
              "cell-style": cellStyle,
            },
            on: {
              "sort-change": sortChange,
              select: select,
              "select-all": selectAll,
              "selection-change": selectionChange,
            },
            style: {
              width: "100%",
            },
          },
          this.renderColumn(tableColumn, h)
        ),
        h("el-pagination", {
          props: {
            "current-page": current,
            "page-size": pageSize,
            "page-sizes": pageSizes,
            total: total,
            layout: "total, sizes, prev, pager, next, jumper",
          },
          on: {
            "size-change": sizeChange,
            "current-change": currentChange,
          },
          style: {
            "text-align": "right",
            "margin-top": "10px",
          },
        }),
      ]
    );
  }

methods部分

    createColumn(column) {
      return [
        "el-table-column",
        {
          props: {
            ...this.attributesColumn(column),
          },
        },
      ];
    },
    createScopedSlot({ scopedSlots, prop }, h) {
      let { tag, text, textStyle, click, slot } = scopedSlots;
      let { isTrue, isFunc, columnClick, scopedSlotText } = this;
      return {
        scopedSlots: {
          default: (props) => {
            const value = props["row"][prop];
            if (slot) {
              return h(
                "div",
                this.$scopedSlots["opration"]({
                  row: props["row"],
                })
              );
            }
            return h(
              tag,
              {
                on: isTrue(click) ? { click: columnClick(props) } : {},
                style:
                  isFunc(textStyle) &&
                  textStyle(props["$index"] + 1, value, props["row"]),
              },
              scopedSlotText(text, value, props)
            );
          },
        },
      };
    },
    renderColumn(columns, h) {
      if (!columns || !columns.length) {
        return;
      }
      let tableColumn = [];
      columns.forEach((item) => {
        let { scopedSlots } = item;
        let column = this.createColumn(item);
        let vNode;
        if (this.notEmpty(scopedSlots)) {
          column[1] = {
            ...column[1],
            ...this.createScopedSlot(item, h),
          };
          vNode = h(...column);
        } else {
          vNode = h(...column, this.renderColumn(item.children, h));
        }
        tableColumn.push(vNode);
      });
      return tableColumn;
    },
    attributesColumn(column) {
      const attr = [
        "type",
        "label",
        "prop",
        "width",
        "align",
        "fixed",
        "sortable",
        "scopedSlots",
      ];
      let obj = {};
      attr.forEach((prop) => {
        column[prop] && (obj[prop] = column[prop]);
      });
      return obj;
    },
    notEmpty(obj) {
      if (typeof obj === "object" && JSON.stringify(obj) !== "{}") {
        return true;
      }
      return false;
    },
    isTrue(v) {
      return v === true;
    },
    isDef(v) {
      return v === undefined;
    },
    isFunc(v) {
      return typeof v === "function";
    },
    scopedSlotText(text, value, column) {
      const { $index: index, row } = column;
      if (!this.isDef(text)) {
        return this.isFunc(text) ? text(index, value, row) : text;
      }
      return value;
    },
    //自定义列点击
    columnClick(scope) {
      let { $index, row } = scope;
      return () => {
        this.$emit("columnClick", {
          index: $index,
          ...row,
        });
      };
    },
    //当表格的排序条件发生变化的时候会触发该事件
    sortChange({ order, prop }) {
      this.$emit("sortChange", { order, prop });
    },
    //当用户手动勾选数据行的 Checkbox 时触发的事件
    select(selection, row) {
      this.$emit("select", selection, row);
    },
    //当用户手动勾选全选 Checkbox 时触发的事件
    selectAll(selection) {
      this.$emit("select-all", selection);
    },
    //当选择项发生变化时会触发该事件
    selectionChange(selection) {
      this.$emit("selection-change", selection);
    },
    //pageSize 改变时会触发
    sizeChange(size) {
      this.$emit("pagination-change", {
        current: this.pagination.current,
        size,
      });
    },
    //currentPage 改变时会触发
    currentChange(current) {
      this.$emit("pagination-change", {
        current,
        size:this.pagination.size,
      });
    },

用法

基础用法

<render-table />

效果

image.png

    <render-table
      :border="true"
      :tableColumn="tableColumn"
      :tableData="tableData"
    />
    tableColumn: [
        {
          label: "序号",
          type: "index",
          width: 50,
          align: "center",
        },
        {
          label: "学生",
          prop: "student",
          align: "center",
        },
        {
          label: "数学",
          align: "center",
          prop: "math",
        },
        {
          label: "语文",
          align: "center",
          prop: "language",
        },
        {
          label: "英语",
          prop: "english",
          align: "center",
        },
      ],
      tableData: [
        {
          student: "张三",
          math: 60,
          language: 60,
          english: 60,
        },
        {
          student: "王五",
          math: 66,
          language: 80,
          english: 77,
        },
      ],

通过设置tableColumntableData属性来给表格传递数据。

效果

image.png

多选

    <render-table
      :border="true"
      :tableColumn="tableColumn"
      :tableData="tableData"
      @select='select'
      @select-all='selectAll'
      @selection-change='selectionChange'
    />
    {
      // label: "序号",
      type: "selection",
      width: 50,
      align: "center",
    },

实现多选只需要将type属性值设置为selection。同时提供了selectselect-allselection-change等方法。用法和el-table一致。

效果

image.png

支持表头嵌套

        {
          label: "成绩",
          align: "center",
          children: [
            {
              label: "数学",
              align: "center",
              prop: "math",
            },
            {
              label: "语文",
              align: "center",
              prop: "language",
            },
            {
              label: "英语",
              prop: "english",
              align: "center",
            },
          ],
        },

实现表头嵌套需要在tableColumn中添加children属性即可。

效果

image.png

支持自定义列

        {
          label: "学生",
          prop: "student",
          align: "center",
          scopedSlots:{
            tag:'span',
            text:'自定义'
          }
        },

要实现自定义列需要添加scopedSlots并设置tagtext。其中tag指标签,text是自定义显示文本。text可以是string或者function

text为函数时

        {
          label: "学生",
          prop: "student",
          align: "center",
          scopedSlots:{
            tag:'span',
            text:(index,value,row)=>{
              return value;
            }
          }
        },

效果

image.png

如果需要设置样式则有textStyle

     {
          label: "学生",
          prop: "student",
          align: "center",
          scopedSlots:{
            tag:'span',
            text:(index,value,row)=>{
              return value;
            },
            textStyle:(index,value,row)=>{
              return {color:'red'}
            }
          }
        },

效果

image.png

需要监听点击回调时有clickcolumnClick

        <render-table
          :border="true"
          :tableColumn="tableColumn"
          :tableData="tableData"
          @select="select"
          @select-all="selectAll"
          @selection-change="selectionChange"
          @columnClick="columnClick"
        />
        
        {
          label: "学生",
          prop: "student",
          align: "center",
          scopedSlots:{
            tag:'span',
            text:(index,value,row)=>{
              return value;
            },
            textStyle:(index,value,row)=>{
              return {color:'red'}
            },
            click:true
          }
        },
         columnClick(row) {
          console.log(row);
        },

一般来说表格点击都是和操作有关,例如编辑,你可以这样。

        {
          label:'操作',
          align:'center',
          scopedSlots:{
            tag:'el-button',
            click:true,
            text:'编辑'
          }
        }

效果

image.png

到这里看起来是不是感觉还阔以,但是操作这里还是有一些问题。如果只使用tag+text+click配合的话是没法弄一些复杂的操作的,例如同时有编辑,删除,查看等按钮。这时就得使用slot这个属性了。

    <render-table
      :border="true"
      :tableColumn="tableColumn"
      :tableData="tableData"
      @select="select"
      @select-all="selectAll"
      @selection-change="selectionChange"
      @columnClick="columnClick"
    >
    <template #opration='{row}'>
      <el-button @click="edit(row)">编辑</el-button>
      <el-button>查看</el-button>
      <el-button>删除</el-button>
    </template>
    </render-table>
    
    {
      label:'操作',
      align:'center',
      scopedSlots:{
        slot:true
      }
    }
        

效果

image.png

属性

没特别说明的用法和el-table一样。

table

          data: tableData,
          size: size,
          stripe: stripe,
          border: border,
          fit: fit,
          "show-header": showHeader,
          "header-cell-style": headerCellStyle,
          "cell-style": cellStyle,
          

column

        "type",
        "label",
        "prop",
        "width",
        "align",
        "fixed",
        "sortable",
        "scopedSlots", 

scopedSlots

        tag: 元素标签,需要自定义时必填。
        text:自定义文本渲染,可以是string/functionfunction将接收三个参数index,value,row
        textStyle:自定义样式function接收三个参数index,value,row,返回值object
        click:是否需要点击,true/false
        slot:开启插槽,true/false

pagination

            "current-page": current,
            "page-size": pageSize,
            "page-sizes": pageSizes,
            total: total,

事件

    columnClick(row)  //自定义点击回调
    //和el-table用法一样
    sortChange,
    select,
    selectAll,
    selectionChange,
    //分页回调
    pagination-change({current,size})

全部代码

//renderTable.vue
<script>
export default {
  name: "Render-Table",
  props: {
    tableColumn: {
      type: Array,
    },
    tableData: {
      type: Array,
    },
    size: {
      type: String,
      default: "medium",
    },
    stripe: {
      type: Boolean,
      default: false,
    },
    border: {
      type: Boolean,
      default: false,
    },
    fit: {
      type: Boolean,
      default: true,
    },
    showHeader: {
      type: Boolean,
      default: true,
    },
    headerCellStyle: {
      type: Function,
      default: (/*{ row, column, rowIndex, columnIndex }*/) => {
        return { color: "#000" };
      },
    },
    cellStyle: {
      type: Function,
      default: (/** {row, column, rowIndex, columnIndex}*/) => {
        return { color: "#333" };
      },
    },
    pagination: {
      type: Object,
      default: () => ({
        current: 1,
        size: 10,
        total: 0,
      }),
    },
    pageSizes: {
      type: Array,
      default: () => [10, 20, 30, 50],
    },
  },
  render(h) {
    const {
      tableData,
      size,
      stripe,
      border,
      fit,
      showHeader,
      headerCellStyle,
      cellStyle,
      sortChange,
      select,
      selectAll,
      selectionChange,
      tableColumn,
      // 分页
      pagination: { current, size: pageSize, total },
      pageSizes,
      sizeChange,
      currentChange,
    } = this;
    return h(
      "div",
      {
        class: {
          "render-table": true,
        },
      },
      [
        h(
          "el-table",
          {
            props: {
              data: tableData,
              size: size,
              stripe: stripe,
              border: border,
              fit: fit,
              "show-header": showHeader,
              "header-cell-style": headerCellStyle,
              "cell-style": cellStyle,
            },
            on: {
              "sort-change": sortChange,
              select: select,
              "select-all": selectAll,
              "selection-change": selectionChange,
            },
            style: {
              width: "100%",
            },
          },
          this.renderColumn(tableColumn, h)
        ),

        h("el-pagination", {
          props: {
            "current-page": current,
            "page-size": pageSize,
            "page-sizes": pageSizes,
            total: total,
            layout: "total, sizes, prev, pager, next, jumper",
          },
          on: {
            "size-change": sizeChange,
            "current-change": currentChange,
          },
          style: {
            "text-align": "right",
            "margin-top": "10px",
          },
        }),
      ]
    );
  },
  methods: {
    createColumn(column) {
      return [
        "el-table-column",
        {
          props: {
            ...this.attributesColumn(column),
          },
        },
      ];
    },
    createScopedSlot({ scopedSlots, prop }, h) {
      let { tag, text, textStyle, click, slot } = scopedSlots;
      let { isTrue, isFunc, columnClick, scopedSlotText } = this;
      return {
        scopedSlots: {
          default: (props) => {
            const value = props["row"][prop];
            if (slot) {
              return h(
                "div",
                this.$scopedSlots["opration"]({
                  row: props["row"],
                })
              );
            }
            return h(
              tag,
              {
                on: isTrue(click) ? { click: columnClick(props) } : {},
                style:
                  isFunc(textStyle) &&
                  textStyle(props["$index"] + 1, value, props["row"]),
              },
              scopedSlotText(text, value, props)
            );
          },
        },
      };
    },
    renderColumn(columns, h) {
      if (!columns || !columns.length) {
        return;
      }
      let tableColumn = [];
      columns.forEach((item) => {
        let { scopedSlots } = item;
        let column = this.createColumn(item);
        let vNode;
        if (this.notEmpty(scopedSlots)) {
          column[1] = {
            ...column[1],
            ...this.createScopedSlot(item, h),
          };
          vNode = h(...column);
        } else {
          vNode = h(...column, this.renderColumn(item.children, h));
        }
        tableColumn.push(vNode);
      });
      return tableColumn;
    },
    attributesColumn(column) {
      const attr = [
        "type",
        "label",
        "prop",
        "width",
        "align",
        "fixed",
        "sortable",
        "scopedSlots",
      ];
      let obj = {};
      attr.forEach((prop) => {
        column[prop] && (obj[prop] = column[prop]);
      });
      return obj;
    },
    notEmpty(obj) {
      if (typeof obj === "object" && JSON.stringify(obj) !== "{}") {
        return true;
      }
      return false;
    },
    isTrue(v) {
      return v === true;
    },
    isDef(v) {
      return v === undefined;
    },
    isFunc(v) {
      return typeof v === "function";
    },
    scopedSlotText(text, value, column) {
      const { $index: index, row } = column;
      if (!this.isDef(text)) {
        return this.isFunc(text) ? text(index, value, row) : text;
      }
      return value;

    },
    //自定义列点击
    columnClick(scope) {
      let { $index, row } = scope;
      return () => {
        this.$emit("columnClick", {
          index: $index,
          ...row,
        });
      };
    },
    //当表格的排序条件发生变化的时候会触发该事件
    sortChange({ order, prop }) {
      this.$emit("sortChange", { order, prop });
    },
    //当用户手动勾选数据行的 Checkbox 时触发的事件
    select(selection, row) {
      this.$emit("select", selection, row);
    },
    //当用户手动勾选全选 Checkbox 时触发的事件
    selectAll(selection) {
      this.$emit("select-all", selection);
    },
    //当选择项发生变化时会触发该事件
    selectionChange(selection) {
      this.$emit("selection-change", selection);
    },
    //pageSize 改变时会触发
    sizeChange(size) {
      this.$emit("pagination-change", {
        current: this.pagination.current,
        size,
      });
    },

    //currentPage 改变时会触发
    currentChange(current) {
      this.$emit("pagination-change", {
        current,
        size: this.pagination.size,
      });
    },
  },

};
</script>

以上就是全部代码了,就是把各个部分的代码组合起来而已。我也不清楚为什么你们都说跑不起来。