封装Element-UI的Table
在开发后台管理系统时,一个常见的页面布局是:上面一堆过滤搜索条件,下面一个表格显示搜索结果。
而这个表格通常要满足几点需求:
- 表头可以显示图标
- 带复选框
- 每行行尾有操作按钮如删除、修改
- 分页
在之前的项目里,我直接用Element-UI提供的组件,开发起来比较繁琐。尤其是团队其他人参与进来,每个人实现的风格可能不大一致,后期维护起来也比较痛苦。
而且新的项目里多了这样一个需求:用户可以配置显示哪些列。也就是说列不是写死的,是动态的。原来的模式不能满足。
因此在新的项目里我对表格功能进行了封装,这样在使用表格时配置一下Column和数据来源就可以了。而且团队其他人用的风格也一致了。
下面记录一下我是如何封装的。
组件
main.vue
<template>
<div>
<!--
父组件没有在$props中声明的属性和方法会通过$attrs、$listeners直接传递下去,这两个属性在封装透明的组件时特别有用
这里的重点是把外面传进来的column数组通过自定义的ElColumn组件渲染成实际的el-table-column
-->
<el-table
:key="key"
ref="elTable"
v-loading="$attrs.loading"
v-bind="$attrs"
v-on="$listeners"
:data="data">
<ElColumn v-for="(col, index) in column" :key="index" :column="col"></ElColumn>
</el-table>
<!--
由于$attrs给上面用了,这里用一个pagination的object来接收属性,避免冲突
-->
<el-pagination
v-if="pagination"
v-bind="pagination"
v-on="$listeners"
@current-change="handleCurrentPageChange"
@size-change="handleSizeChange"
:current-page.sync="currentPage"
:page-size.sync="pageSize"
:total="total"
:layout="pagination.layout || 'total, prev, pager, next, jumper'"
:style="{'margin-top': pagination.top || '3px', 'text-align': pagination.align || 'center'}">
</el-pagination>
</div>
</template>
<script>
import ElColumn from './column.vue'
export default {
name: 'ElTableWrap',
components: {
ElColumn
},
props: {
column: Array,
data: Array,
getData: Function,
isGetDataOnCreate: { type: Boolean, default: true },
pagination: { type: Object, default: null }
},
data() {
return {
key: 1,
currentPage: 1,
pageSize: (this.pagination && this.pagination.pageSize) || 10,
total: 0
}
},
created() {
if (this.isGetDataOnCreate && this.getData) this.getTableData()
},
methods: {
// 有时候父节点宽度改变时,表格没有自适应,重新渲染一下
reRenderTable() {
this.key += 1
this.$refs.elTable.doLayout()
},
// 查询后台获取某一页的数据
getTableData(page_num, page_size) {
this.currentPage = page_num || 1
if (page_size) this.pageSize = page_size
this.$emit('update:loading', true)
this.getData(this.currentPage, this.pageSize).then(res => {
this.$emit('update:loading', false)
if (res.status) {
this.total = res.total
this.$emit('update:data', res.data)
}
})
},
refreshCurrentPage() {
this.getTableData(this.currentPage)
},
handleCurrentPageChange(page_num) {
this.getTableData(page_num)
},
handleSizeChange(page_size) {
this.getTableData(1, page_size)
}
}
}
</script>
column.vue
<template>
<!--
这个组件主要是把传进来的column渲染成真正需要的el-table-column
第一种情况是column包含了render函数,需要用expand-dom这个函数式组件把render函数渲染出来
-->
<el-table-column
v-if="column.render || column.renderHeader"
v-bind="column">
<template slot="header" slot-scope="scope">
<expand-dom
v-if="column.renderHeader"
:scope="scope"
:render="column.renderHeader">
</expand-dom>
<span v-else>{{ scope.column.label }}</span>
</template>
<template slot-scope="scope">
<expand-dom
v-if="column.render"
:scope="scope"
:render="column.render">
</expand-dom>
<span v-else>{{ scope.row[scope.column.property] }}</span>
</template>
<!-- 这里渲染树形数据 -->
<template v-if="column.children">
<ElColumn v-for="(col, index) in column.children" :key="index" :column="col"></ElColumn>
</template>
</el-table-column>
<!-- 另一种情况column里面没有包含render函数 -->
<el-table-column
v-else
v-bind="column">
<template v-if="column.children">
<ElColumn v-for="(col, index) in column.children" :key="index" :column="col"></ElColumn>
</template>
</el-table-column>
</template>
<script>
export default {
name: 'ElColumn',
components: {
// 这里定义了一个functonal component,把column里面的render函数渲染出来
expandDom: {
functional: true,
props: {
scope: Object,
render: Function
},
render(h, context) {
return context.props.render(h, context.props.scope)
}
}
},
props: {
column: Object
}
}
</script>
用法
<template>
<ElTableWrap
ref="table"
size="mini"
:loading.sync="isLoadingTableData"
:column="column"
:data.sync="tableData"
:get-data="getTableData"
:pagination="pagination"
highlight-current-row
@select="onSelect"
@select-all="onSelect">
</ElTableWrap>
</template>
<script>
import getData from 'somewhere'
import ElTableWrap from './main.vue'
export default {
name: 'Test',
components: {
ElTableWrap
},
data() {
return {
isLoadingTableData: false,
tableData: [],
tableColumn: [
{type: 'selection', width: 40},
{prop: 'foo', label: 'Foo', width: 70, fixed: true},
{prop: 'bar', label: 'Bar', width: 70, formatter: this.formatBar},
{width: 100, renderHeader: this.renderHeader, render: this.render, fixed: 'right'}
],
selection: [],
pagination: {
pageSize: 20,
top: '5px'
}
}
},
methods: {
getTableData(page_num, page_size) {
return getData({page_num, page_size})
},
refreshTable() {
this.$refs.table.getTableData()
},
refreshCurrentPage() {
this.$refs.table.refreshCurrentPage()
},
onSelect(selection, row) {
this.selection = selection
},
formatBar(row, column, cellValue, index) {
return cellValue
},
renderHeader(h, scope) {
return h('i',
{
class: 'el-icon-menu',
style: 'font-size: 18px; cursor: pointer;',
on: { click: () => { console.log('header button clicked') } }
}
)
},
render(h, scope) {
return h('el-button',
{
on: { click: () => { console.log('clicked button on row: ', scope.row)} }
},
'action'
)
}
}
}
</script>
可以看到,只要把data里面的tableColumn配置好,引进获取数据的接口getData,就可以了。因为这个封装相对于里面的el-table差不多是透明的,更多用法只要查阅el-table的api,相应地设置props属性、tableColumn和监听事件即可。而分页相关的功能修改data里面的pagination对象即可。
最后render函数也可以使用JSX替代手写。
总结
经过了简单的二次封装,我们可以通过配置数组生成需要的表格。满足了可以动态配置列的需求。也提升了团队的开发效率。