Skip to content
On this page

基于 Element-plus Table 封装的易用, 一致, 友好的 Vue Table 组件

Why

用过 Element UI Table 的同学都知道用 Table 组件时需要用到el-table-column,它是和 html 混写在一起的, 如果很多列的话,就需要一个个写配置, 否则就需要用到循环。如果列配置内容有根据不同条件展示不同样式内容的话, 就需要在插槽里面做判断, 比如这样两种编辑状态:

普通形式表格编辑

普通形式表格编辑

比如在插槽里面做配置, 如下代码:

<el-table-column label="操作">
  <template #default="scope">
    <div v-if="scope.row._edit">
      <el-button type="primary" @click="handleSave(scope)"> 保存 </el-button>
      <el-button @click="handleCancle(scope)"> 取消 </el-button>
    </div>
    <el-button v-else type="primary" @click="handleEdit(scope)">
      编辑
    </el-button>
  </template>
</el-table-column>
<el-table-column label="操作">
  <template #default="scope">
    <div v-if="scope.row._edit">
      <el-button type="primary" @click="handleSave(scope)"> 保存 </el-button>
      <el-button @click="handleCancle(scope)"> 取消 </el-button>
    </div>
    <el-button v-else type="primary" @click="handleEdit(scope)">
      编辑
    </el-button>
  </template>
</el-table-column>

以上这样代码也不算太复杂。 但是假如我们的需求是要切换两个 column 的顺序, 比如: 切換列表格 那应该怎么实现呢, 大概思路就是抽离列配置成为一个 columns 数组, 用 v-for 遍历 columns, 然后在插槽里面做每一列的判断。 通過控制数组顺序来调整列顺序。大概代码思路如下:

完整的在线 Demo

<el-table-column
  v-for="column in columns"
  :prop="column.prop"
  :label="column.label"
>
  <template #default="scope">
    <div v-if="column.prop === 'name'">
      <el-input
        v-if="scope.row._edit"
        v-model="scope.row[scope.column.property]"
      ></el-input>
      <span v-else>{{ scope.row[scope.column.property] }}</span>
    </div>
    <div v-if="column.prop === 'tag'">
      <el-select
        v-if="scope.row._edit"
        v-model="scope.row[scope.column.property]"
        style="width: 120px"
      >
        <el-option
          v-for="option in tagOptions"
          :label="option.label"
          :value="option.value"
        ></el-option>
      </el-select>
      <el-tag v-else :type="scope.row.tag === '家' ? 'info' : 'success'">
        {{ scope.row.tag }}
      </el-tag>
    </div>
  </template>
</el-table-column>
<el-table-column
  v-for="column in columns"
  :prop="column.prop"
  :label="column.label"
>
  <template #default="scope">
    <div v-if="column.prop === 'name'">
      <el-input
        v-if="scope.row._edit"
        v-model="scope.row[scope.column.property]"
      ></el-input>
      <span v-else>{{ scope.row[scope.column.property] }}</span>
    </div>
    <div v-if="column.prop === 'tag'">
      <el-select
        v-if="scope.row._edit"
        v-model="scope.row[scope.column.property]"
        style="width: 120px"
      >
        <el-option
          v-for="option in tagOptions"
          :label="option.label"
          :value="option.value"
        ></el-option>
      </el-select>
      <el-tag v-else :type="scope.row.tag === '家' ? 'info' : 'success'">
        {{ scope.row.tag }}
      </el-tag>
    </div>
  </template>
</el-table-column>

JS 代码如下:

const columns = ref([
  {
    prop: 'date',
    label: '日期',
  },
  {
    prop: 'name',
    label: '姓名',
  },
  {
    prop: 'tag',
    label: '标签',
  },
]);
const toggleColumnSort = () => {
  columns.value = [
    ...columns.value.slice(0, 1),
    columns.value[2],
    columns.value[1],
    ...columns.value.slice(3),
  ];
};
const columns = ref([
  {
    prop: 'date',
    label: '日期',
  },
  {
    prop: 'name',
    label: '姓名',
  },
  {
    prop: 'tag',
    label: '标签',
  },
]);
const toggleColumnSort = () => {
  columns.value = [
    ...columns.value.slice(0, 1),
    columns.value[2],
    columns.value[1],
    ...columns.value.slice(3),
  ];
};

痛点

以上代码可以看出一些问题:

  • 判断太多, 而且互相嵌套在一块
  • 每一列的配置互相耦合,不利于后期的维护
  • html 代码和 js 代码混合在一起, 不利于书写

ElTableNext

为了解决以上问题, 封装了基于 el-table 的 ElTableNext 组件。 以上的代码用 ElTableNext 可以如下实现:

在线 Demo

<el-table-next :column="column" :data="tableData" />
<el-table-next :column="column" :data="tableData" />

tsx 代码如下:

const column = ref([
  {
    prop: 'date',
    label: '日期',
  },
  {
    prop: 'name',
    label: '姓名',
    render: (value, scope) =>
      scope.row._edit ? (
        <el-input
          model-value={value}
          onUpdate:modelValue={(val) => {
            scope.row[scope.column.property] = val;
          }}
        />
      ) : (
        value
      ),
  },
  {
    prop: 'tag',
    label: '标签',
    render: (value, scope) =>
      scope.row._edit ? (
        <el-select
          model-value={value}
          style='width: 120px'
          onUpdate:modelValue={(val) => {
            scope.row[scope.column.property] = val;
          }}
        >
          {tagOptions.map((option) => (
            <el-option label={option.label} value={option.value}></el-option>
          ))}
        </el-select>
      ) : (
        <el-tag type={scope.row.tag === '家' ? 'info' : 'success'}>
          {value}
        </el-tag>
      ),
  },
]);
const toggleColumnSort = () => {
  column.value = [
    ...column.value.slice(0, 1),
    column.value[2],
    column.value[1],
    ...column.value.slice(3),
  ];
};
const column = ref([
  {
    prop: 'date',
    label: '日期',
  },
  {
    prop: 'name',
    label: '姓名',
    render: (value, scope) =>
      scope.row._edit ? (
        <el-input
          model-value={value}
          onUpdate:modelValue={(val) => {
            scope.row[scope.column.property] = val;
          }}
        />
      ) : (
        value
      ),
  },
  {
    prop: 'tag',
    label: '标签',
    render: (value, scope) =>
      scope.row._edit ? (
        <el-select
          model-value={value}
          style='width: 120px'
          onUpdate:modelValue={(val) => {
            scope.row[scope.column.property] = val;
          }}
        >
          {tagOptions.map((option) => (
            <el-option label={option.label} value={option.value}></el-option>
          ))}
        </el-select>
      ) : (
        <el-tag type={scope.row.tag === '家' ? 'info' : 'success'}>
          {value}
        </el-tag>
      ),
  },
]);
const toggleColumnSort = () => {
  column.value = [
    ...column.value.slice(0, 1),
    column.value[2],
    column.value[1],
    ...column.value.slice(3),
  ];
};

设计原则

  1. 完全兼容el-table 的原有相关 API 和事件, 方法等, 保持一致。
  2. 通过 JSON 来配置列表结构

此外, 增加了一些额外的特性, 提供更好的 typescript 提示。因此封裝的時候,花了很多功夫去折腾 el-table typescript 类型。比如:

  1. 代碼提示1
  2. 代碼提示2

渲染复杂列内容提供两种方式:

  • 一种是插槽 Slot,
  • 一种是 render 函数。render 函数的话需要开始lang="tsx" tsx 对于熟悉react开发者来说,会更加亲切。但是也失去 vue template 提供的便利。比如v-model. 你需要自己在model-value={value}中设置value, 在onUpdate:modelValue={(val) => {}}中改变 value 值

看下跳转查看如下 Demo:

  1. render-方式使用
  2. slot-方式使用

源码封装

本质就是利用 tsx 对 el-table 进行封装, 并且提供了一些额外的特性。对 render el-table-column 的時候判断是 slot 还是 render 函数。 总代码量包括类型辅助的代码不超过两百分代码 -- 代码跳转 github

ElTableNext 分别实现了官方el-table 所有 demo,应该还是比较稳的。

Demo

可跳转查看如下 Demo: https://el-table-next.vercel.app/guide/example.html

Other

此外还提供了 PlayGround 给小伙伴玩耍

基于 Element-plus Table 封装的易用, 一致, 友好的 Vue Table 组件 has loaded