之前把 Element-ui的 Table结合 Pagination简单封装成了一个组件,经过一段时间的使用,为了更好地适配自己的项目,断断续续做了一些调整,稍微饱满了一点点,更好用了一点点。
(ps:这是适配的 vue2.0,如果使用 vue3.0,对 slot部分需要做一下调整)
2023.03.17更新:简化 table高度的设置方法,支持简单的跨页选择,支持排序,优化tableRenderHeader
方法,添加自定义单选框,添加序号列默认自定义方法
2022.12.02更新:当表头数据不固定需要动态渲染时,会出现添加 fixed属性的列出现错位的情况。
解决方法:监听 columns,在 columns发生变化时,使用 doLayout
方法对 table进行重新布局。具体见下图:
添加【表头不换行】的方法:通过render-header
属性自定义表头渲染方法。具体见下方tableRenderHeader
方法
!!!修补一个bug:最后一页删掉所有数据后,分页器 current自动更新为前一页,但无法触发 current-change事件,以至于分页器显示在前一页,但是 table中无数据。
——试来试去也没有定位到问题所在,就想了个比较被动的办法,在监听到 data为空且 current不为1时,再请求一次数据,不过这个请求需要在 current被自动更新之后,所以需要用到 $nextTick。具体见下图:
1.【Table】组件
<template>
<div v-loading="loading">
<el-table
ref="tableData"
:row-key="rowKey"
:border="border"
:stripe="stripe"
:max-height="maxHeight"
:size="tableSize"
:data="data"
@selection-change="handleSelectionChange"
@current-change="handleTableCurrentChange"
@row-click="handleTableRowClick"
@sort-change="handleSortChange"
v-bind="otherConfig">
<template v-for="(item, index) in columns">
<el-table-column
v-if="item.selection"
type="selection"
width="50"
:fixed="item.fixed"
:reserve-selection="reserveSelection"
align="center"
header-align="center"
:key="`selection_${index}`">el-table-column>
<el-table-column
v-else-if="item.singleSelection"
width="50"
:fixed="item.fixed"
align="center"
header-align="center"
:key="`singleSelection_${index}`">
<template slot-scope="scope">
<el-checkbox v-model="scope.row.isSingleSelected" @change="handleSingleSelectionChange(scope.row)">el-checkbox>
template>
el-table-column>
<el-table-column
v-else-if="item.index"
type="index"
width="80"
:fixed="item.fixed"
label="序号"
:index="item.indexMethod || indexMethod"
:key="`index_${index}`">el-table-column>
<el-table-column
v-else-if="item.multi"
align="center"
:label="item.label"
:key="`multi_${index}`">
<el-table-column
v-for="(child, childIndex) in item.children"
:key="`child_${index}_${childIndex}`"
v-bind="child">
el-table-column>
el-table-column>
<slot
v-else-if="item.slot"
:name="item.slot">slot>
<el-table-column
v-else
show-overflow-tooltip
v-bind="item"
:fixed="item.fixed"
:min-width="item.minWidth"
:key="`normal_${index}`"
:render-header="tableRenderHeader">el-table-column>
template>
el-table>
<el-pagination
v-if="isPaginationShow && pagination.total"
class="opagination mt12"
background
layout="sizes, prev, pager, next"
:page-sizes="[10, 20, 50, 100]"
:current-page.sync="pagination.current"
:page-size="pagination.size"
:total="pagination.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange">
el-pagination>
<slot name="custom-content">slot>
div>
template>
<script>
export default {
name: 'Table',
props: {
columns: {
type: Array,
default: () => [],
},
data: {
type: Array,
default: () => [],
},
pagination: {
type: Object,
default: () => ({
current: 1,
size: 10,
total: 0,
}),
},
isPaginationShow: {
type: Boolean,
default: true,
},
maxHeight: {
type: [Number, String],
default: 440,
},
tableSize: {
type: String,
default: 'small',
},
stripe: {
type: Boolean,
default: true,
},
border: {
type: Boolean,
default: false
},
otherConfig: {
type: Object,
default: () => {},
},
loading: {
type: Boolean,
default: false,
},
// 支持跨页选择的两个配置项
rowKey: {
type: String,
default: ''
},
reserveSelection: {
type: Boolean,
default: false
}
},
data() {
return {};
},
mounted () {
window.addEventListener('resize', this.handleWindowResize)
},
beforeDestroy () {
window.removeEventListener('resize', this.handleWindowResize)
},
methods: {
// 切换页码
handleCurrentChange() {
this.$emit('getData');
},
// 切换每页条数
handleSizeChange(value) {
// current-page和 page-size都支持 .sync修饰符,用了.sync修饰符,就不需要手动给 this.pagination赋值了
this.pagination.size = value;
this.$emit('getData');
},
// 切换选择
handleSelectionChange(val) {
this.$emit('changeSelection', val);
},
// 单选
handleTableCurrentChange(currentRow) {
this.$emit('changeCurrent', currentRow);
},
// 自定义单选
handleSingleSelectionChange (currentRow) {
this.$emit('changeSingleSelection', currentRow)
},
// 点击行
handleTableRowClick(currentRow) {
this.$emit('rowClick', currentRow);
},
// 排序
handleSortChange (val) {
this.$emit('changeSort', val)
},
// 自定义表头渲染方法
tableRenderHeader(h, { column }) {
const span = document.createElement('span')
span.innerText = column.label
document.body.appendChild(span)
const realWidth = span.getBoundingClientRect().width
let columnWidth = realWidth + 22 // 22: 左右padding 10*2 + border预留 2
if (column.sortable === 'custom') columnWidth += 30
column.minWidth = columnWidth
document.body.removeChild(span)
return h('span', column.label)
},
// 序号列自定义方法
indexMethod (index) {
const { current, size } = this.pagination
return (current - 1) * size + index + 1
}
// 页面缩放重新计算 table高度
handleWindowResize () {
this.$emit('windowResize')
},
},
watch: {
data() {
// 重新请求数据时 table滚动到顶部
this.$refs.tableData.$refs.bodyWrapper.scrollTop = 0;
// 最后一页删掉所有数据后,data为空,分页器 current自动更新为前一页,但无法触发 current-change事件,此处手动判断重新请求数据
if (newVal && !newVal.length && this.pagination.current !== 1) {
this.$nextTick(() => {
this.$emit('getData');
});
}
},
columns: {
handler(newVal) {
console.log(newVal);
if (newVal && newVal.length) {
this.$nextTick(() => {
this.$refs.tableData.doLayout();
});
}
},
deep: true,
},
},
};
</script>
2.父组件调用
<template>
<div ref="tableModuleContant">
<div ref="moduleHeader">
<el-form
ref="searchForm"
:model="searchForm"
label-width="80px">
<el-form-item label="名称" prop="name">
<el-input v-model="searchForm.name" placeholder="请输入">el-input>
el-form-item>
<el-form-item>
<el-button
type="primary"
@click="onSearch"
:disabled="loading">查询el-button>
<el-button @click="onReset" :disabled="loading">重置el-button>
el-form-item>
el-form>
div>
<Table
ref="searchTable"
:border="true"
rowKey="id"
:reserveSelection="true"
:columns="columns"
:data="tableList"
:pagination="pagination"
:loading="loading"
@getData="fetchTableList"
@changeSort="handleTableSortChange"
@changeSelection="handleTableSelectionChange"
@changeSingleSelection="handleTableSelectionChange"
:maxHeight="tableMaxHeight"
@windowResize="calcTableHeight">
<el-table-column slot="action" fixed="right" label="操作" width="100">
<template
slot-scope="scope">
<el-button
type="text"
@click="showDetail(scope.row)">查看详情el-button>
template>
el-table-column>
Table>
div>
template>
<script>
export default {
data() {
return {
// 查询条件
searchForm: {
name: '',
},
// 每次请求缓存的查询参数
searchCache: {},
columns: [
{ selection: true }, // 多选,和自定义单选正常情况下不可能同时存在的,此处只是一个demo
{ singleSelection: true }, // 自定义单选
{
index: true,
indexMethod(index) {
return index + 1;
},
},
{ prop: 'name', label: '名称', width: 160 },
...
{ slot: 'action' },
],
tableList: [],
pagination: {
current: 1,
size: 20,
total: 0,
},
dataSelected: null, // 选中的数据
sortRule: null, // 排序规则
tableMaxHeight: 440,
loading: false
};
},
mounted () {
this.calcTableHeight()
},
methods: {
// 获取数据
fetchTableList() {
this.loading = true;
const { current, size } = this.pagination;
let params = { current, current, ...this.searchCache };
if (this.sortRule) {
const { prop, order } = this.sortRule;
params = { prop, order };
}
fetchApi(params).then((res) => {
if (res.code === 200) {
const { list = [], total = 0 } = res.data;
this.tableList = list || [];
this.pagination.total = total;
}
}).finally(() => {
this.loading = false;
});
},
// 点击”查询“按钮 => 此时缓存查询参数
onSearch() {
// 缓存查询参数
this.searchCache = { ...this.searchForm };
// 初始化分页器
this.pagination = {
current: 1,
size: 20,
total: 0,
};
// 初始化排序
this.$refs.searchTable.$refs.tableData.clearSort()
this.sortRule = null
// 请求
this.fetchTableList();
},
// 重置
onReset() {
this.$refs.searchForm.resetFields(); // 有时候不生效,就用下面的方法手动初始化
Object.keys(this.searchForm).forEach(key => {
if (this.searchForm[key] instanceof Array) this.searchForm[key] = []
else this.searchForm[key] = ''
})
},
// 排序
handleTableSortChange (val) {
const { order, prop } = val
if (order && prop) this.sortRule = { prop, order }
else this.sortRule = null
this.fetchTableList()
},
// 多选选中数据
handleTableSelectionChange (val) {
this.dataSelected = [...val]
},
// 单选选中数据
handleTableSelectionChange (val) {
const { isSingleSelected, id } = val
if (isSingleSelected) {
this.tableList.forEach(v => {
if (v.id !== id) v.isSingleSelected = false
})
this.dataSelected = val
} else {
this.dataSelected = null
}
},
// 计算 table高度
calcTableHeight () {
this.$nextTick(() => {
// 最后的 100,是预留的可能存在的 padding margin之类的,以及分页器的高度,根据实际情况计算
this.tableMaxHeight = this.$refs.tableModuleContant.clientHeight - this.$refs.moduleHeader.clientHeight - 100
})
},
// 操作
showDetail(row) {
console.log(row);
// ...
},
},
};
</script>