基于ElementUI二次封装Table实现动态生成Column

时间:2020-9-8 作者:admin

前端小白,请多指教。原文地址

个人感觉写 Vue 比较烦人的是写模版,比如使用 ElementUI 的 Table 组件,有多少列就要写多少个 Column,比较繁琐。而用 JS 代码则可以很灵活,庆幸的是 Vue 是支持渲染函数的,意味着可以用 JS 代码去实现组件。

首先明确一下我们的封装目标

  1. 对于一般的只用作显示的列,只要指定 prop 和 label 即可自动生成。
  2. 保留原来 Column 的 slot 能力,用户能自定义显示列。
  3. 自定义数据格式化器。
  4. 可选的控制某列是否显示。

使用和效果

先来看看最后能用这个二次封装的组件干些什么

简单数据展示

如果是单纯的数据显示,那么只要这样写

<EluiDynTable :desc="tableDesc" :data="tableData" />

然后把需要哪些列以及数据给它即可,个人感觉是非常方便的

export default {
  name: "app",
  components: {
    EluiDynTable,
  },
  data() {
    return {
      tableDesc: [
        { prop: "name", label: "名字" },
        { prop: "city", label: "城市" },
        { prop: "born", label: "出生时间", formatter: "ts" },
      ],
      tableData: [
        {
          name: "Alice",
          city: "Shanghai",
          born: 946656000000,
        },
        {
          name: "Bob",
          city: "Hongkong",
          born: 946699994000,
        },
      ],
    };
  },
};

效果如下

基于ElementUI二次封装Table实现动态生成Column

自定义列

如果要自定义列,比如要在最右测添加功能操作按钮,则可以这样

<EluiDynTable :desc="tableDesc" :data="tableData">
  <EluiDynColumn prop="operation">
    <span slot="header"> 自定义 </span>
    <div slot-scope="{ row, $index }">
      <el-button type="danger" size="mini" @click="handleClick(row, $index)"
        >删除</el-button
      >
    </div>
  </EluiDynColumn>
</EluiDynTable>
export default {
  name: "app",
  components: {
    EluiDynTable,
    EluiDynColumn,
  },
  data() {
    return {
      tableDesc: [
        { prop: "name", label: "名字" },
        { prop: "city", label: "城市" },
        { prop: "born", label: "出生时间", formatter: "ts" },
        { prop: "operation", label: "操作", fixed: "right" },
      ],
      tableData: [
        {
          name: "Alice",
          city: "Shanghai",
          born: 946656000000,
        },
        {
          name: "Bob",
          city: "Hongkong",
          born: 946699994000,
        },
      ],
    };
  },
  methods: {
    handleClick(row, index) {
      /* eslint-disable no-console */
      console.log(`deleting ${row.name} at ${index}`);
    },
  },
};

效果图

基于ElementUI二次封装Table实现动态生成Column

控制列显示

不大建议使用。调用组件的 toggle 函数,函数签名如下

toggle(prop, hidden);

prop 用来指定哪一列。如果不指定 hidden 值(boolean),则是在显示和不显示之间来回切换,如果指定则用指定值。

实现

我们首先要给各种数据类型准备一些常用的格式化器,我开箱内置里一些,具体代码查看这里:

function formatNumber(prop) {}

function formatArray(array, extra) {}

function formatTimestamp(ts) {}

function formatSecond(second) {}

function formatterByType(prop) {}

export function format({ formatter, prop, scope, extra }) {}

如果内置的格式化器不能满足要求,也是可以自定义的,给 formatter 传递custom,然后在extra里指定一个formatter,它应该是一个函数类型,具体的函数签名是这样的。

extra.formatter(prop, scope);

对于在项目中会广泛使用的格式化器则可以全局注册,提供了 API:

setFormatter(type, formatter);

然后我们对 ElColumn 进行简单的封装,default slot会默认调用格式化器对数据进行格式化展示,而header slot则会显示label值。注意把原来的 slot 再暴露出去,这样在使用的时候,就可以自定义了。

<template>
  <el-table-column v-bind="$attrs">
    <template slot="header" slot-scope="h">
      <slot name="header" v-bind="h">{{ h.column.label }}</slot>
    </template>
    <template slot-scope="scope">
      <slot v-bind="scope">
        <span>{{ formatRow(scope.row[prop], scope) }}</span>
      </slot>
    </template>
  </el-table-column>
</template>
<script>
import { format } from "../utils/format";

export default {
  name: "EluiDynColumn",
  props: ["prop", "formatter", "extra"],
  methods: {
    formatRow(prop, scope) {
      return format({
        formatter: this.formatter,
        prop,
        scope,
        extra: this.extra,
      });
    },
  },
};
</script>

最后使用渲染函数,实现一个 Table 组件

const EluiDynTable = {
  name: "EluiDynTable",

  props: {
    data: {
      type: Array,
      default: () => [],
    },
    desc: {
      type: Array,
      default: () => [],
    },
  },
  methods: {
    toggle(prop, hidden) {
      const d = this.desc.find((e) => e.prop === prop);
      if (d) {
        if (hidden !== undefined) {
          d.hidden = !!hidden;
        } else {
          d.hidden = !d.hidden;
        }
        this.$forceUpdate();
      }
    },
  },
  render: function (h) {
    const isDynColumn = (c) =>
      c.componentOptions &&
      c.componentOptions.Ctor.extendOptions.name === "EluiDynColumn";
    const dynColumns = (this.$slots.default || []).filter(isDynColumn);
    const keyOf = (c) => c.componentOptions.propsData.prop;
    const columnGroups = group(dynColumns, keyOf);

    const children = [];
    for (let d of this.desc) {
      if (d.hidden) {
        continue;
      }
      let child = columnGroups[d.prop];
      if (child) {
        const propKeys = Object.keys(child.componentOptions.Ctor.options.props);
        for (let k in d) {
          if (propKeys.indexOf(k) >= 0) {
            child.componentOptions.propsData[k] = d[k];
          } else {
            child.data.attrs[k] = d[k];
          }
        }
      } else {
        const propKeys = Object.keys(EluiDynColumn.props);
        const props = {};
        const attrs = {};
        for (let k in d) {
          if (propKeys.indexOf(k) >= 0) {
            props[k] = d[k];
          } else {
            attrs[k] = d[k];
          }
        }
        child = h(EluiDynColumn, {
          props,
          attrs,
        });
      }
      children.push(child);
    }
    return h(Table, { props: this.$props, attrs: this.$attrs }, children);
  },
};

可以看到的代码细节是,我只会筛选出default slot里的EluiDynColumn组件,认为这是自定义的列,然后遍历表格描述desc,如果用户有自定义组件,则使用自定义组件,否则自动生成 EluiDynColumn 组件。给 EluiDynColumn 的propsattrs赋值,因为 EluiDynColumn 会把 attrs 通过v-bind绑定到 ElColumn 上,因此完美保留了原来 ElColumn 的特性。还有一个细节就是 hidden 属性,这是用来控制列显示的。render 函数最后返回一个 ElTable 的 VNode 即可。

完整的代码查看这里

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。