通过组件将表格变为可隐藏列表格的实现方式
需求背景及实现思路
需求背景
对于一个表格,有些时候,不同用户所关注的列是不一样的,比如说表格中存在ABC三列,用户甲关注的是AC列,用户乙关注的是BC列,这个时候,用户就会希望通过配置,使得表格中只呈现出自己希望看到的数据,减少其他无关列的干扰。
实现思路
数据保存方式
因为只是用户自定义的隐藏列信息,不涉及一些隐私性较强的数据,因此选择把用户的隐藏列信息保存到 localStorage
中。
可隐藏列数据获取
如何获取表格中有哪些列,我想了两种方法:
第一种方法:js配置
通过js来配置表格有哪些列,然后通过 v-for
来进行 el-table-column
的渲染,这样就很容易知道表格有哪些列了,直接拿用来遍历的配置数据就可以了。
这种方式灵活性会高一点,也容易转变成自己想要的数据结构。但是这次不想这样做,想试试其他方法。
第二种方法:组件
这种方法是通过定义一个组件,然后把 el-table
传入到组件的插槽中,然后通过插槽获取到 el-table
的组件实例,然后再继续获取 el-table
的插槽,也就是 el-table-column
的集合,然后遍历集合,从它们的 propData
中取得 label
和 prop
,进而组装成可隐藏列数据。
这种方法可以保留模板写法的习惯,只需要在 el-table
外面套一层组件即可。获取可隐藏列数据的逻辑都写在组件内部。
列显示到隐藏的实现
如果是采用js配置的方式,那么可以在 v-for
的过程中控制列的隐藏,也可以结合全部列的数据和已隐藏列的数据,过滤出要显示出来的列,然后再进行渲染。
如果是采用组件的方式,那么可以在获取 el-table-column
集合之后,将被隐藏的列从集合中删除掉即可。
列隐藏到显示的实现
采用js的方式就无需多说了,直接把列从已隐藏列中删除,然后重新渲染表格即可。
但是如果是采用组件的方式,那么从列从隐藏到显示就有点不一样了,还有待解决。
需求实现
可隐藏列表格组件
组件模板内容如下
<template>
<div class="hidden-table">
<el-popover placement="left" width="150" trigger="hover">
<!-- 表格占位 -->
<i class="el-icon-setting" slot="reference"></i>
</el-popover>
<div class="table-bar">
<slot name="bar"></slot>
</div>
<div class="table-body">
<slot></slot>
</div>
</div>
</template>
默认插槽是用来放 el-table
的,考虑到有些表格喜欢在顶部加一些按钮,所以多预留在一个 bar
插槽。
组件data
组件的data中存在以下状态
// 可配置列-表格全部可隐藏列都在这里
configurableColumns: [],
// 已隐藏列
hiddenColumns: [],
// 当前勾选的隐藏列
selection: [],
// 被删除的elTableColumn节点
deletedNodes: []
configurableColumns
数组类型,元素类型为 {label:string,prop:string}
,可配置列,表格中存在哪些可以隐藏或者显示的列都放在这里面。
hiddenColumns(废弃)
字符串数组,元素为被隐藏列的 prop
值,里面存放的是不在页面上显示的列的 prop
集合。
selection(废弃)
数组类型,元素类型为 {label:string,prop:string}
,配置表格(用来勾选哪一列隐藏哪一列显示的表格)当前勾选的列,注意,为了符合直观尝试,配置表格中被勾选项表示该列显示,未被勾选项表示该列隐藏。
deletedNodes(废弃)
数组类型,elTable.$slots.default
中删除的 elTableColumn
节点的集合,用于隐藏的列重新显示用。
组件methods
组件methods中存在如下方法:
init()
组件初始化方法,在组件的 mounted
生命周期中执行。
该方法的最终目的是为了给 configurableColumns
状态赋值,并且进行第一次的表格隐藏列隐藏操作。
init() {
// 获取elTable组件实例
const elTable = this.getElTableComp()
// 如果没获取到elTable组件实例,就不继续执行
if (!elTable) return
// 获取elTableColumn集合
const elTableColumns = this.getElTableColumns(elTable)
// 转换数据格式
const columns = this.transformColumns(elTableColumns)
// 可配置列赋值
this.configurableColumns = columns
// 进行列隐藏
this.handleHiddenColumns(elTableColumns)
}
getElTableComp()
该方法会从当前组件的默认插槽中获取到 el-table
组件的实例,并返回。
getElTableComp() {
// 获取默认插槽里的内容
const {$slots: {default: defaultSlot}} = this
// find未找到会返回undefined
const elTable = defaultSlot.find(VNode => VNode.componentOptions.tag === 'el-table')
return elTable ? elTable.componentInstance : void 0
}
getElTableColumns()
获取elTable插槽中的elTableColumn集合。
getElTableColumns(elTable) {
// 获取elTable的默认插槽-这里面存放着el-table-column集合
const {$slots: {default: elTableDefaultSlot}} = elTable
return elTableDefaultSlot
}
transformColumns()
转换数据格式,通过遍历elTableColumn,转换出带label和prop的对象组成的数组。
transformColumns(elTableColumns) {
const columns = []
elTableColumns.forEach(VNode => {
const {propsData: {label, prop}} = VNode.componentOptions
if (label && prop) {
columns.push({label, prop})
}
})
return columns
}
handleHiddenColumns()
处理隐藏列,将需要被隐藏的列从 elTableColumn
集合中删除。
handleHiddenColumns(elTableColumns) {
// 从缓存中获取 hiddenColumns ,如果没有,说明是第一次,第一次是显示全部列的,则不执行删除操作
const jsonHidden = localStorage.getItem('hidden-columns')
if (!jsonHidden) return void 0
// 解析JSON-hidden里面存放的都是prop
const hidden = JSON.parse(jsonHidden)
// hidden长度为0也不继续执行
if (hidden.length === 0) return void 0
// 开始删除节点
hidden.forEach(hiddenProp => {
const index = elTableColumns.findIndex(VNode => {
const {propsData: {prop}} = VNode.componentOptions
return prop && hiddenProp === prop
})
if (index > -1) {
elTableColumns.splice(index, 1)
}
})
}
onSelectionChange()
配置表格勾选事件,这个方法中将被隐藏列存入缓存中。
onSelectionChange(selection) {
// 勾选的都是要显示的
const list = selection.map(i => i.prop)
const hidden = this.configurableColumns.filter(i => !list.includes(i.prop)).map(i => i.prop)
localStorage.setItem('hidden-columns', JSON.stringify(hidden))
}
onPopoverShow()
弹出框显示事件,这个方法用来对配置表格进行回显。
onPopoverShow() {
// 从缓存中获取 hiddenColumns
const hidden = this.getHiddenFromStorage()
// 对表格进行回显
const table = this.$refs.columnTable
this.configurableColumns.forEach(i => {
if (hidden.includes(i.prop)) {
// 一定要写上true 否则就会在勾选和未勾选中不停的切换
table.toggleRowSelection(i, false)
} else {
table.toggleRowSelection(i, true)
}
})
}
onPopoverHide()
弹出框隐藏事件,这个方法用于刷新组件,使得组件的生命周期重新走一次。
onPopoverHide() {
this.$emit('render')
}
当弹出框关闭的时候,会触发组件的 render
事件,然后父组件则在 render
事件中改变该组件的 key
,进而使得该组件重新渲染。
<hidden-table @render="hiddenComp=Date.now()" :key="hiddenComp">
...
</hidden-table>
getHiddenFromStorage()
从缓存中获取隐藏列的配置。算是工具函数,为了简化代码抽离出来的。
getHiddenFromStorage() {
const jsonHidden = localStorage.getItem('hidden-columns')
if (!jsonHidden) return []
// 解析JSON
return JSON.parse(jsonHidden)
}
使用组件
在父组件中引入组件,然后在组件中写下 elTable
,如下:
<hidden-table @render="hiddenComp=Date.now()" :key="hiddenComp">
<el-table :data="list" style="width: 100%" ref="elTable">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column label="姓名" prop="name"></el-table-column>
<el-table-column label="启动Id" prop="sysId"></el-table-column>
<el-table-column label="日期" prop="date"></el-table-column>
<el-table-column label="年龄" prop="age"></el-table-column>
</el-table>
</hidden-table>
然后就可以在页面上看到效果了。
通过上面的视频可以看到,基本上需求是实现了,接下来就是一些细节方面的优化了,比如说样式的调整,用户体验方面的调整了。
细节优化
样式调整
正常来说,我们会把设置按钮放到表格的右上角,通过将 .table-body
设为相对定位,然后将 .setting-icon
设为绝对定位,将设置按钮定位到表格的右上角。
.table-body {
position: relative;
}
.table-body .setting-icon {
position: absolute;
top: 15px;
right: 15px;
z-index: 1;
}
要把设置按钮的 z-index
设的大一点,否则就会被表格给遮挡住了,但是也不要太大,后面需要加loading动画。
调整后效果图如下。
逻辑调整
有些逻辑方面也应该调整,比如说,假设有用户把全部列都给隐藏了,那么这种操作是不应该出现的,就应该提醒用户不要这样做。
onSelectionChange(selection) {
// 判断用户是否全部勾选了
if (selection.length === 0) {
// 提示用户不要全部列都隐藏了
this.$notify({
title: '警告',
message: '全部列都隐藏了你想看什么?看黑丝吗?',
type: 'warning'
});
return void 0 // 本次操作无效-保留一列
}
// 勾选的都是要显示的
const list = selection.map(i => i.prop)
const hidden = this.configurableColumns.filter(i => !list.includes(i.prop)).map(i => i.prop)
localStorage.setItem('hidden-columns', JSON.stringify(hidden))
}
再比如说,假设我只是点开了配置表格,但是没有做任何操作,那么我就没必要重新渲染表格。
这里本来想用个变量标识的,但是发现要把
selection-change
事件给改成select
,就懒得动了。
写在最后
到这里,需求基本上实现了,但是弊端也很明显,目前只能用于普通的表格,处理不了多级表头这些,而且还有一个需求,列排序,这个需求很经常会跟着列隐藏一起出现的,但是目前的组件也处理不了。
还有就是,如果 el-table-column
是通过 v-for
渲染出来的,也就是动态列,好像也有问题。
虽然上面说的问题都比较棘手,但是按照上面组件的思路,无非也是对 elTableColumn
组件的删除和排序,理清楚了还是可以做到的。
这次这个方法也只是一个尝试,尝试完之后,如果让我在js和组件之间选的话,我还是更倾向于js。
转载自:https://juejin.cn/post/7247733201925374010