背景

element-ui 中的 el-table 组件只提供了数据展示,而分页功能作为一个单独的组件 el-pagination,并没有像 Ant Design 一样集成到 el-able 组件中,并且每一个数据列也都是一个单独的组件 el-table-column

在开发过程中就出现了一个很明显的问题:业务中的大部分数据表格功能都类似,数据列通常在3-6个不等,且都需要分页,前端分页与后端分页的逻辑又不相同,这就导致了每次写一个这样数据表格功能,都需要至少复制粘贴很多 el-table-column 组件和一套实现分页功能的代码。写代码最恼火的就是要复制粘贴很多重复的代码,一不小心就漏了些什么,有木有……

结合我最早使用的 easyui 中的 datagrid 组件,在 element-ui的基础上封装一个能满足大部分业务功能的 el-datagrid 组件,能大大的提高开发效率。

EasyUI 的 datagrid

接触的第一个 UI 框架是 jQuery 版本的 EasyUI,虽然样式是丑了一点,但个人觉得它的组件的封装,对开发人员是很友好的,基本上每个组件都可以用一个 对象或数组 去动态配置,很符合 数据驱动 的思维。

功能

大部分的业务(增删改查)中,数据表格通常有以下几个功能:

  1. 数据展示
  2. 排序
  3. 对每一行的操作(查看、修改、删除)
  4. 对整个数据表格的操作(新增、批量删除)
  5. 分页(前端 / 后端)
  6. 刷新数据

数据列配置(功能1、2、3 )

通常会从远程加载数据,将数据列配置成 数组,数组的每一项匹配远程数据中对象的属性,替代写多个 el-table-column 组件的麻烦。

远程数据格式:

{
    data: [
        {
            name: 'lixiao 1',
            email: 'lixiao 1@163.com'
        },
        ……
        {
            name: 'lixiao 12',
            email: 'lixiao 12@163.com'
        }
    ],
    total: 12
}

数据列配置如下:

columns: [
    {
        label: '姓名',
        prop: 'name',
        sortable: true
    }, {
        label: '邮箱',
        prop: 'email',
        sortable: true
    }, {
        label: '操作',
        actions: [
            {
                label: '修改',
                prop: 'edit',
                type: 'warning',
                icon: 'el-icon-edit'
            }, {
                label: '删除',
                prop: 'delete',
                type: 'danger',
                icon: 'el-icon-delete'
            }
        ]
    }
]

label 表示列对应的表头,prop 表示列对应的远程数据中对象的属性,sortable 表示该列是否支持排序,actions 通常在最后一列,渲染一组操作按钮,采用 this.$emit('rowClick', { row, prop }) 的方式,向父组件 App.vue 传递一个 rowClick 事件,参数是一个包含了 该行数据点击按钮的 prop(例如 edit / delete)的对象,在父组件中监听 rowClick 事件,判断 prop 的值即可做出不同的响应。

工具栏配置(功能4)

数据列配置能解决数据 增删改查 中的删、改、查,通常会在表格上方提供一组功能性按钮,例如新增、导出(部分 / 全量)、批量删除等功能,比较特殊的是批量删除这种功能需要获取到已经选中的行的数据。

toolbar: [
    {
        label: '新增',
        prop: 'add',
        type: 'primary',
        icon: 'el-icon-plus',
    }, {
        label: '删除多行',
        prop: 'delete',
        type: 'danger',
        icon: 'el-icon-delete',
        usableAfterSelect: true
    }
]

同样使用 this.$emit('toolbarClick', { rows, prop }) 的方式向父组件传递一个 toolbarClick 事件,不同的是,rows 是一个包含了多行数据的数组(当然也可能是一个空数组),父组件中监听 toolbarClick 事件,判断 prop 值和 rows 长度即可做出不同的响应。

对于需要 至少选择一行 才能进行操作的功能,参考了 Ant Design 例子中的交互,设置 usableAfterSelect 属性为 true 之后,选中至少一行之前按钮不可用。

clipboard.png

clipboard.png

分页(功能5)

前端分页

第一次获取数据之后,切换页码、切换每页数量只需要利用 slice 截取原始数据数组中对应数量的数据。

后端分页

每次切换页码和切换每页数量都需要重新获取数据,将返回的全部数据都作为要显示的原始数据。

// ElDatagrid.vue
// this.index 表示显示的数据的序号,表头为 #

<el-table :data="showData" />

computed: {
    showData() {
        if (this.clientPagination) {
            // 若客户端分页,则根据页码和每页数量 截取相应的数据
            return this.data.slice().splice(this.index - 1, this.pageSize);
        } else {
            // 若服务端分页,则直接显示获取到的数据
            return this.data.slice();
        }
    }
},

刷新数据(功能6)

刷新数据指仅刷新数据表格的数据,而不是刷新整个页面。而触发刷新的操作往往是由父组件发起的,就变成了,如何让父组件通知子组件重新发请求获取数据。

实现这个功能我尝试了好几种方法:

  • 最难看的 bus 中央事件总线。每个子组件实例都与父组件有一个不冲突的事件名做关联,用起来不方便
  • 在子组件中 watch 一个传递过来的 prop 值的变化,那父组件需要更新数据的时候还是需要改变那个传递给子组件的值,嗯,new Date() 肯定是一直在变的……使用仍然不是很方便
  • 父组件中通过 $refs 访问子组件,调用子组件的方法。原来有这么简单又好用的方法
<el-datagrid ref="datagrid" />

methods: {
    reloadDatagrid() {
        this.$refs['datagrid'].reload();
    }
}

完整的 ElDatagrid 组件和使用 demoAPI 文档:https://github.com/lixiao564/...


李逍
69 声望6 粉丝