likes
comments
collection
share

通过组件将表格变为可隐藏列表格的实现方式

作者站长头像
站长
· 阅读数 19

需求背景及实现思路

需求背景

对于一个表格,有些时候,不同用户所关注的列是不一样的,比如说表格中存在ABC三列,用户甲关注的是AC列,用户乙关注的是BC列,这个时候,用户就会希望通过配置,使得表格中只呈现出自己希望看到的数据,减少其他无关列的干扰。

实现思路

数据保存方式

因为只是用户自定义的隐藏列信息,不涉及一些隐私性较强的数据,因此选择把用户的隐藏列信息保存到 localStorage 中。

可隐藏列数据获取

如何获取表格中有哪些列,我想了两种方法:

第一种方法:js配置

通过js来配置表格有哪些列,然后通过 v-for 来进行 el-table-column 的渲染,这样就很容易知道表格有哪些列了,直接拿用来遍历的配置数据就可以了。

这种方式灵活性会高一点,也容易转变成自己想要的数据结构。但是这次不想这样做,想试试其他方法。

第二种方法:组件

这种方法是通过定义一个组件,然后把 el-table 传入到组件的插槽中,然后通过插槽获取到 el-table 的组件实例,然后再继续获取 el-table 的插槽,也就是 el-table-column 的集合,然后遍历集合,从它们的 propData 中取得 labelprop,进而组装成可隐藏列数据。

这种方法可以保留模板写法的习惯,只需要在 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。